mod bundled;
mod migrations;
mod paths;
mod project;
mod settings;
mod trust;
mod version;
use elph_agent::{DatabaseSpec, InitProgress, ensure_databases, ensure_dirs};
pub use paths::Paths;
pub type InitError = elph_agent::InitError;
pub type Result<T> = std::result::Result<T, InitError>;
const INIT_STEPS: u64 = 4;
pub async fn ensure(app_version: &str) -> Result<Paths> {
let progress = InitProgress::new(INIT_STEPS).with_quiet_env("ELPH_QUIET");
progress.advance("Resolving home directories");
let paths = Paths::resolve()?;
run_init_steps(&paths, app_version, &progress).await?;
progress.finish();
Ok(paths)
}
#[allow(dead_code)]
pub async fn ensure_with_paths(paths: &Paths, app_version: &str) -> Result<()> {
let progress = InitProgress::new(INIT_STEPS).with_quiet_env("ELPH_QUIET");
run_init_steps(paths, app_version, &progress).await?;
progress.finish();
Ok(())
}
pub fn ensure_blocking(app_version: &str) -> Result<Paths> {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.map_err(|err| InitError::Io(std::io::Error::other(err)))?
.block_on(ensure(app_version))
}
async fn run_init_steps(paths: &Paths, app_version: &str, progress: &InitProgress) -> Result<()> {
progress.advance("Creating directories");
ensure_layout_dirs(paths)?;
progress.advance("Writing configuration");
ensure_files(paths, app_version)?;
progress.advance("Initializing databases");
ensure_databases(&[
DatabaseSpec {
path: &paths.metadata_db_path(),
migrations: migrations::metadata_migrations(),
},
DatabaseSpec {
path: &paths.memory_db_path(),
migrations: migrations::memory_migrations(),
},
])
.await?;
Ok(())
}
fn ensure_layout_dirs(paths: &Paths) -> Result<()> {
let mut dirs = vec![paths.config_dir().clone(), paths.data_dir().clone()];
dirs.extend(paths.required_dirs());
ensure_dirs(&dirs)
}
fn ensure_files(paths: &Paths, app_version: &str) -> Result<()> {
settings::Settings::ensure(paths)?;
trust::TrustStore::ensure(paths)?;
version::VersionFile::ensure(paths, app_version)?;
bundled::BundledManifest::ensure(paths, app_version)?;
project::ensure(paths)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn ensure_creates_full_layout() {
let tmp = tempfile::tempdir().expect("tempdir");
let config = tmp.path().join("config");
let data = tmp.path().join("data");
let project = tmp.path().join("repo");
let paths = Paths::from_dirs(config, data, project);
ensure_with_paths(&paths, "0.0.10-test").await.expect("ensure layout");
assert!(paths.settings_path().exists());
assert!(paths.trust_path().exists());
assert!(paths.version_path().exists());
assert!(paths.bundled_manifest_path().exists());
assert!(paths.metadata_db_path().exists());
assert!(paths.memory_db_path().exists());
assert!(paths.project_gitignore_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());
}
}