wsx-core 0.21.0

Composable project, Git worktree, terminal runtime, hook, and model primitives for wsx.
Documentation
use super::{paths, IntegrationTarget};
use std::path::Path;

fn commands(target: IntegrationTarget) -> &'static [&'static str] {
    match target {
        IntegrationTarget::Pi => &["pi"],
        IntegrationTarget::Omp => &["omp"],
        IntegrationTarget::Claude => &["claude"],
        IntegrationTarget::Codex => &["codex"],
        IntegrationTarget::Copilot => &["copilot"],
        IntegrationTarget::Devin => &["devin"],
        IntegrationTarget::Droid => &["droid"],
        IntegrationTarget::Kimi => &["kimi"],
        IntegrationTarget::Opencode => &["opencode"],
        IntegrationTarget::Kilo => &["kilo", "kilo-code"],
        IntegrationTarget::Hermes => &["hermes"],
        IntegrationTarget::Qodercli => &["qodercli"],
        IntegrationTarget::Qwen => &["qwen"],
        IntegrationTarget::Cursor => &["cursor-agent"],
        IntegrationTarget::Mastracode => &["mastracode"],
        IntegrationTarget::AntigravityCli => &["agy"],
        IntegrationTarget::Grok => &["grok"],
    }
}

#[cfg(unix)]
fn executable(path: &Path) -> bool {
    use std::os::unix::fs::PermissionsExt;
    path.metadata()
        .is_ok_and(|m| m.is_file() && m.permissions().mode() & 0o111 != 0)
}
#[cfg(not(unix))]
fn executable(path: &Path) -> bool {
    path.is_file()
}

fn available_on_path(target: IntegrationTarget, path: Option<std::ffi::OsString>) -> bool {
    path.as_deref()
        .into_iter()
        .flat_map(std::env::split_paths)
        .any(|dir| {
            commands(target)
                .iter()
                .any(|cmd| executable(&dir.join(cmd)))
        })
}

pub fn is_available(target: IntegrationTarget) -> bool {
    if available_on_path(target, std::env::var_os("PATH")) {
        return true;
    }
    match target {
        IntegrationTarget::Codex => paths::root(target).is_ok_and(|root| {
            glob::glob(&format!(
                "{}/packages/standalone/releases/*/bin/codex",
                root.display()
            ))
            .ok()
            .into_iter()
            .flatten()
            .filter_map(Result::ok)
            .any(|path| executable(&path))
        }),
        IntegrationTarget::Hermes if cfg!(windows) => paths::root(target).is_ok_and(|root| {
            [
                root.join("hermes.exe"),
                root.join("bin/hermes.exe"),
                root.join("Scripts/hermes.exe"),
            ]
            .iter()
            .any(|path| executable(path))
        }),
        _ => false,
    }
}

#[cfg(test)]
pub(crate) fn available_on_path_for_test(target: IntegrationTarget, path: &Path) -> bool {
    available_on_path(target, Some(path.as_os_str().to_owned()))
}