malvin 0.2.6

Non-interactive research and coding agent
#[cfg(unix)]
use std::process::Command;
#[cfg(unix)]
use std::time::{Duration, Instant};

#[cfg(unix)]
use super::child_wait::{spawn_piped_process_group, wait_child_with_timeout};

#[cfg(unix)]
pub const LIVE_AGENT_CMD_TIMEOUT: Duration = Duration::from_mins(5);

pub fn require_openrouter_key_when_gate_set(gate_name: &str) {
    assert!(
        std::env::var_os("OPENROUTER_API_KEY").is_some_and(|v| !v.is_empty()),
        "{gate_name}=1 requires OPENROUTER_API_KEY (fail-closed; not a soft skip)"
    );
}

#[cfg(unix)]
pub fn live_agent_prereqs_met() -> bool {
    malvin::agent_or_cursor_agent_bin().is_some() && live_agent_auth_available()
}

#[cfg(unix)]
fn live_agent_auth_available() -> bool {
    for key in ["CURSOR_AGENT_API_KEY", "CURSOR_API_KEY", "AGENT_API_KEY"] {
        if std::env::var_os(key).is_some_and(|v| !v.is_empty()) {
            return true;
        }
    }
    ["agent", "cursor-agent"].into_iter().any(|bin| {
        std::process::Command::new(bin)
            .args(["auth", "status"])
            .stdout(std::process::Stdio::null())
            .stderr(std::process::Stdio::null())
            .status()
            .is_ok_and(|s| s.success())
    })
}

#[cfg(unix)]
pub fn command_output_live_agent(cmd: &mut Command) -> std::io::Result<std::process::Output> {
    cmd.env_remove("MALVIN_TEST_NO_REAL_AGENT");
    cmd.env_remove("MALVIN_AGENT_ACP_BIN");
    cmd.env_remove("HERDR_ENV");
    cmd.env_remove("HERDR_SOCKET_PATH");
    cmd.env_remove("HERDR_PANE_ID");
    let (child, stdout_jh, stderr_jh) = spawn_piped_process_group(cmd)?;
    wait_child_with_timeout(
        child,
        stdout_jh,
        stderr_jh,
        Instant::now() + LIVE_AGENT_CMD_TIMEOUT,
    )
}