Crate renames: - zesdex-entities::seaorm → domain (misleading name, no SeaORM used) - zesdex-dto → merged into zesdex-entities (100% re-exports) - zesdex-libs → zesdex-infra (vague name) Module renames: - app/harness → guard (misleading: safety gatekeeper, not test harness) - runtime/commands → action_dispatch (name clashed with controller/command) - resources → prompts (embedded prompt text, not general resources) - tool/seqthink → sequential_think (unreadable abbreviation) - msglog/query → insert (module only inserts, never queries) Dead code removal: - app/mode/help.rs (orphaned — not declared in mod.rs) - app/mode/loading.rs (orphaned — not declared in mod.rs) File splitting (71 new files, avg ~115 lines/file): - app/runtime/actions/: 1→8 files (was 2030 lines) - view/overlays/: 1→16 files (was 1167 lines) - tool/lsp/: 1→8 per-tool files (was 909 lines) - main.rs: 1→5 files (session, daemon, attach, event_loop) - workflow/engine + hive_mind: 2→10 files - subagent/engine + auto: 2→9 files - lsp/provisioner: 1→5 files - review/: 1→6 files - guard/: 1→2 files (extracted patterns) - state/misc: 1→3 files (input, scroll) - mcp/: 1→3 files (transport, adapter) - stream/json_repair extracted from turn.rs DRY: - Pattern constants (STUB_PATTERNS etc) in guard/patterns shared with subagent - 3 near-identical background spawners → 1 generic + thin wrappers - Shared spawn_subagent_with_drain() extracted - Shared create_session() in main - write_osc52 deduplicated Bug fixes: - archive_message(): sess.db → db (wrong variable name) - execute_one_tool(): wrong parameter name - check_credential_read() function was missing (restored from test expectations)
121 lines
3.8 KiB
Rust
121 lines
3.8 KiB
Rust
//! Environment discovery: finding binaries on PATH and detecting available
|
|
//! toolchains / package managers on the host system.
|
|
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
|
|
/// Rust toolchain availability on the host PATH.
|
|
#[derive(Debug, Clone)]
|
|
pub struct RustToolchain {
|
|
pub has_rustup: bool,
|
|
pub has_cargo: bool,
|
|
}
|
|
|
|
/// Web / scripting language toolchain availability.
|
|
#[derive(Debug, Clone)]
|
|
pub struct WebToolchain {
|
|
pub has_npm: bool,
|
|
pub has_go: bool,
|
|
pub has_java: bool,
|
|
}
|
|
|
|
/// General-purpose platform utilities.
|
|
#[derive(Debug, Clone)]
|
|
pub struct PlatformUtils {
|
|
pub has_curl: bool,
|
|
pub has_tar: bool,
|
|
}
|
|
|
|
/// Pacman and Brew package managers (Arch / macOS).
|
|
#[derive(Debug, Clone)]
|
|
pub struct PacmanBrew {
|
|
pub has_pacman: bool,
|
|
pub has_brew: bool,
|
|
}
|
|
|
|
/// Apt and DNF package managers (Debian / Fedora).
|
|
#[derive(Debug, Clone)]
|
|
pub struct AptDnf {
|
|
pub has_apt: bool,
|
|
pub has_dnf: bool,
|
|
}
|
|
|
|
/// Snapshot of the host environment used to decide which install tiers are viable.
|
|
///
|
|
/// Populated by `detect_env()` once per `provision_all_with_progress()` call so we
|
|
/// don't re-shell out for every server. `is_linux` / `is_macos` are
|
|
/// computed at startup (compile time would also work, but keeping the
|
|
/// shape uniform with the rest of the struct makes the call sites tidy).
|
|
#[derive(Debug, Clone)]
|
|
pub struct EnvInfo {
|
|
pub rust: RustToolchain,
|
|
pub web: WebToolchain,
|
|
pub platform: PlatformUtils,
|
|
pub pacman_brew: PacmanBrew,
|
|
pub apt_dnf: AptDnf,
|
|
pub is_linux: bool,
|
|
pub is_macos: bool,
|
|
}
|
|
|
|
/// Check whether `binary` exists on PATH by shelling out to `which`.
|
|
///
|
|
/// Flow: `Command::new("which").arg(binary).output()` → on Unix
|
|
/// `which` returns exit 0 + stdout path when found, non-zero
|
|
/// otherwise. We return the first stdout line as the `PathBuf`.
|
|
///
|
|
/// Returns None if `which` itself is missing, fails to spawn, or the
|
|
/// binary is not on PATH. We deliberately don't cache this — it's only
|
|
/// called during provisioning and the results feed into install-tier
|
|
/// gating, which is already cheap.
|
|
pub fn which(binary: &str) -> Option<PathBuf> {
|
|
let output = Command::new("which").arg(binary).output().ok()?;
|
|
if !output.status.success() {
|
|
return None;
|
|
}
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let first = stdout.lines().next()?.trim();
|
|
if first.is_empty() {
|
|
None
|
|
} else {
|
|
Some(PathBuf::from(first))
|
|
}
|
|
}
|
|
|
|
/// Snapshot the host environment: which toolchains and package managers
|
|
/// are available, and what OS we're on.
|
|
///
|
|
/// Flow: shell out to `which` for each tool in parallel (sequentially,
|
|
/// actually — the calls are fast and the ordering doesn't matter)
|
|
/// → set `EnvInfo` flags. Linux/macOS are detected via cfg at
|
|
/// compile time since `which` won't tell us.
|
|
///
|
|
/// Edge case: `which` may not exist on Windows; we guard with cfg so
|
|
/// this only ever runs on Unix-like targets.
|
|
pub fn detect_env() -> EnvInfo {
|
|
EnvInfo {
|
|
rust: RustToolchain {
|
|
has_rustup: which("rustup").is_some(),
|
|
has_cargo: which("cargo").is_some(),
|
|
},
|
|
web: WebToolchain {
|
|
has_npm: which("npm").is_some(),
|
|
has_go: which("go").is_some(),
|
|
has_java: which("java").is_some(),
|
|
},
|
|
platform: PlatformUtils {
|
|
has_curl: which("curl").is_some(),
|
|
has_tar: which("tar").is_some(),
|
|
},
|
|
pacman_brew: PacmanBrew {
|
|
has_pacman: which("pacman").is_some(),
|
|
has_brew: which("brew").is_some(),
|
|
},
|
|
apt_dnf: AptDnf {
|
|
has_apt: which("apt").is_some() || which("apt-get").is_some(),
|
|
has_dnf: which("dnf").is_some(),
|
|
},
|
|
is_linux: cfg!(target_os = "linux"),
|
|
is_macos: cfg!(target_os = "macos"),
|
|
}
|
|
}
|