eli 0.5.1

Ease Lives Instantly — hook-first AI agent framework with multi-channel support
#[cfg(feature = "gateway")]
use std::path::{Path, PathBuf};

#[cfg(feature = "gateway")]
use serde_json::Value;

#[cfg(feature = "gateway")]
pub(crate) fn find_sidecar_dir() -> Option<PathBuf> {
    [
        std::env::var("ELI_SIDECAR_DIR").ok().map(PathBuf::from),
        installed_sidecar_dir(),
        std::env::current_dir().ok().map(|dir| dir.join("sidecar")),
    ]
    .into_iter()
    .flatten()
    .find(|path| sidecar_exists(path))
}

#[cfg(feature = "gateway")]
pub(crate) fn ensure_node_available() -> bool {
    let status = std::process::Command::new("node")
        .arg("--version")
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::null())
        .status();
    status.is_ok()
}

#[cfg(feature = "gateway")]
pub(crate) fn ensure_sidecar_deps(sidecar_dir: &Path, refresh: bool) -> bool {
    if !refresh && sidecar_dir.join("node_modules").exists() {
        return true;
    }
    if refresh && deps_are_current(sidecar_dir).unwrap_or(false) {
        return true;
    }
    run_npm_install(sidecar_dir)
}

#[cfg(feature = "gateway")]
fn installed_sidecar_dir() -> Option<PathBuf> {
    std::env::current_exe()
        .ok()
        .and_then(|path| path.parent().map(|dir| dir.join("sidecar")))
}

#[cfg(feature = "gateway")]
fn sidecar_exists(path: &Path) -> bool {
    path.join("start.cjs").exists()
}

#[cfg(feature = "gateway")]
fn run_npm_install(sidecar_dir: &Path) -> bool {
    std::process::Command::new("npm")
        .arg("install")
        .current_dir(sidecar_dir)
        .status()
        .is_ok_and(|status| status.success())
}

#[cfg(feature = "gateway")]
fn deps_are_current(sidecar_dir: &Path) -> Option<bool> {
    let openclaw = expected_dep_version(sidecar_dir, "openclaw")?;
    let weixin = expected_dep_version(sidecar_dir, "@tencent-weixin/openclaw-weixin")?;
    Some(
        installed_dep_version(sidecar_dir, "openclaw")? == openclaw
            && installed_dep_version(sidecar_dir, "@tencent-weixin/openclaw-weixin")? == weixin,
    )
}

#[cfg(feature = "gateway")]
fn expected_dep_version(sidecar_dir: &Path, package: &str) -> Option<String> {
    let package_json = read_package_json(&sidecar_dir.join("package.json"))?;
    package_json["dependencies"][package]
        .as_str()
        .map(str::to_owned)
}

#[cfg(feature = "gateway")]
fn installed_dep_version(sidecar_dir: &Path, package: &str) -> Option<String> {
    let path = package_json_path(&sidecar_dir.join("node_modules"), package);
    read_package_json(&path)?["version"]
        .as_str()
        .map(str::to_owned)
}

#[cfg(feature = "gateway")]
fn read_package_json(path: &Path) -> Option<Value> {
    let raw = std::fs::read_to_string(path).ok()?;
    serde_json::from_str(&raw).ok()
}

#[cfg(feature = "gateway")]
fn package_json_path(base_dir: &Path, package: &str) -> PathBuf {
    package
        .split('/')
        .fold(base_dir.to_path_buf(), |path, part| path.join(part))
        .join("package.json")
}