kiss-agent 0.0.35

Agent runtime for the kiss coding harness
Documentation
//! Command arguments shared by the agent, SDK, and terminal shell runners.

use std::path::PathBuf;
use tokio::process::Command;

pub fn resolve(shell_path: Option<&str>) -> PathBuf {
    shell_path
        .filter(|path| !path.trim().is_empty())
        .map(PathBuf::from)
        .unwrap_or_else(|| {
            #[cfg(windows)]
            {
                windows_shell()
            }
            #[cfg(not(windows))]
            {
                PathBuf::from("bash")
            }
        })
}

#[cfg(any(windows, test))]
fn windows_shell() -> PathBuf {
    for (variable, directory) in [
        ("ProgramFiles", "Git"),
        ("ProgramFiles(x86)", "Git"),
        ("LOCALAPPDATA", "Programs/Git"),
    ] {
        if let Some(root) = std::env::var_os(variable) {
            let bash = PathBuf::from(root).join(directory).join("bin/bash.exe");
            if bash.is_file() {
                return bash;
            }
        }
    }
    let paths = std::env::var_os("PATH").unwrap_or_default();
    let on_path = |name: &str| {
        std::env::split_paths(&paths)
            .filter(|directory| directory.is_absolute())
            .map(|directory| directory.join(name))
            .find(|path| path.is_file())
    };
    if let Some(bash) = on_path("bash.exe") {
        return bash;
    }
    for (name, variable, relative) in [
        ("pwsh.exe", "ProgramFiles", "PowerShell/7/pwsh.exe"),
        (
            "powershell.exe",
            "SystemRoot",
            "System32/WindowsPowerShell/v1.0/powershell.exe",
        ),
    ] {
        if let Some(shell) = on_path(name) {
            return shell;
        }
        if let Some(root) = std::env::var_os(variable) {
            let shell = PathBuf::from(root).join(relative);
            if shell.is_file() {
                return shell;
            }
        }
    }
    if let Some(cmd) = on_path("cmd.exe") {
        return cmd;
    }
    if let Some(cmd) = std::env::var_os("COMSPEC").map(PathBuf::from)
        && cmd.is_file()
        && cmd
            .file_stem()
            .is_some_and(|name| name.eq_ignore_ascii_case("cmd"))
    {
        return cmd;
    }
    std::env::var_os("SystemRoot")
        .map(|root| PathBuf::from(root).join("System32/cmd.exe"))
        .filter(|path| path.is_file())
        .unwrap_or_else(|| PathBuf::from("cmd.exe"))
}

pub fn command(shell_path: Option<&str>, source: &str, prefix: Option<&str>) -> Command {
    let shell = resolve(shell_path);
    let name = shell
        .file_stem()
        .unwrap_or_default()
        .to_string_lossy()
        .to_ascii_lowercase();
    let source = match prefix {
        Some(prefix) if name == "cmd" => format!("{prefix} & {source}"),
        Some(prefix) => format!("{prefix}\n{source}"),
        None => source.to_string(),
    };
    let mut command = Command::new(shell);
    #[cfg(windows)]
    if name == "cmd" {
        // cmd.exe parses command text itself; C runtime argument escaping is unsuitable.
        command.args(["/D", "/S", "/C"]);
        command.raw_arg(format!("\"{source}\""));
        return command;
    }
    if matches!(name.as_str(), "powershell" | "pwsh") {
        command.args(["-NoProfile", "-NonInteractive", "-Command"]);
    } else {
        command.arg("-c");
    }
    command.arg(source);
    command
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn windows_shell_selection() {
        if std::env::var_os("KISS_TEST_SHELL_PROBE").is_some() {
            #[cfg(windows)]
            for name in [
                "ProgramFiles",
                "ProgramFiles(x86)",
                "LOCALAPPDATA",
                "SystemRoot",
                "COMSPEC",
                "PATH",
            ] {
                let value = std::env::var_os(format!("KISS_TEST_{name}")).unwrap();
                // SAFETY: Windows environment mutation is thread-safe. Keep this
                // inside the probe process so the parent test is not affected.
                unsafe { std::env::set_var(name, value) };
            }
            #[cfg(windows)]
            let shell = command(None, "echo probe", None)
                .as_std()
                .get_program()
                .to_owned();
            #[cfg(not(windows))]
            let shell = windows_shell().into_os_string();
            println!("KISS_SELECTED_SHELL={}", shell.to_string_lossy());
            return;
        }
        let directory = tempfile::tempdir().unwrap();
        let root = directory.path();
        let program_files = root.join("Program Files");
        let windows = root.join("Windows");
        let path = root.join("path");
        let candidates = [
            program_files.join("Git/bin/bash.exe"),
            root.join("Program Files x86/Git/bin/bash.exe"),
            root.join("LocalAppData/Programs/Git/bin/bash.exe"),
            path.join("bash.exe"),
            path.join("pwsh.exe"),
            program_files.join("PowerShell/7/pwsh.exe"),
            path.join("powershell.exe"),
            windows.join("System32/WindowsPowerShell/v1.0/powershell.exe"),
            path.join("cmd.exe"),
            root.join("comspec/cmd.exe"),
            windows.join("System32/cmd.exe"),
        ];
        let probe = |expected: &std::path::Path| {
            let mut process = std::process::Command::new(std::env::current_exe().unwrap());
            process
                .env_clear()
                .args([
                    "--exact",
                    "tools::shell::tests::windows_shell_selection",
                    "--nocapture",
                ])
                .env("KISS_TEST_SHELL_PROBE", "1");
            for (name, value) in [
                ("ProgramFiles", program_files.clone()),
                ("ProgramFiles(x86)", root.join("Program Files x86")),
                ("LOCALAPPDATA", root.join("LocalAppData")),
                ("SystemRoot", windows.clone()),
                ("COMSPEC", root.join("comspec/cmd.exe")),
                ("PATH", path.clone()),
            ] {
                process
                    .env(name, &value)
                    .env(format!("KISS_TEST_{name}"), value);
            }
            let output = process.output().unwrap();
            assert!(output.status.success(), "{output:?}");
            let stdout = String::from_utf8(output.stdout).unwrap();
            let selected = stdout
                .lines()
                .find_map(|line| line.strip_prefix("KISS_SELECTED_SHELL="))
                .unwrap();
            assert_eq!(std::path::Path::new(selected), expected, "{stdout}");
        };

        // No Bash: choose an installed native Windows shell before command submission.
        std::fs::create_dir_all(&path).unwrap();
        std::fs::write(path.join("pwsh.exe"), "fixture").unwrap();
        probe(&path.join("pwsh.exe"));
        std::fs::remove_file(path.join("pwsh.exe")).unwrap();

        for candidate in &candidates {
            std::fs::create_dir_all(candidate.parent().unwrap()).unwrap();
            std::fs::write(candidate, "fixture").unwrap();
        }
        for candidate in &candidates {
            probe(candidate);
            std::fs::remove_file(candidate).unwrap();
        }
        probe(std::path::Path::new("cmd.exe"));
        let explicit = root.join("custom shell.exe");
        assert_eq!(
            command(explicit.to_str(), "echo test", None)
                .as_std()
                .get_program(),
            explicit.as_os_str()
        );
    }

    #[tokio::test]
    async fn preserves_quoted_url_prefix_and_exit_status() {
        let url = "https://cursor.com/loginDeepControl?challenge=test&uuid=test&mode=login";
        let mut cases = vec![(
            None,
            format!("printf '%s\\n' '{url}' \"$KISS_TEST_PREFIX\"; exit 7"),
            "KISS_TEST_PREFIX='prefix value'",
            format!("{url}\nprefix value\n"),
        )];
        if cfg!(windows) {
            cases.extend([
                (
                    Some("cmd.exe"),
                    format!("echo \"{url}\"& exit /b 7"),
                    "echo prefix value",
                    format!("prefix value\n\"{url}\"\n"),
                ),
                (
                    Some("powershell.exe"),
                    format!("Write-Output '{url}'; Write-Output $env:KISS_TEST_PREFIX; exit 7"),
                    "$env:KISS_TEST_PREFIX='prefix value'",
                    format!("{url}\nprefix value\n"),
                ),
            ]);
        }
        for (shell, source, prefix, expected) in cases {
            let output = command(shell, &source, Some(prefix))
                .output()
                .await
                .unwrap();
            assert_eq!(output.status.code(), Some(7), "{shell:?}: {output:?}");
            assert_eq!(
                String::from_utf8(output.stdout)
                    .unwrap()
                    .lines()
                    .map(str::trim_end)
                    .collect::<Vec<_>>(),
                expected.lines().collect::<Vec<_>>(),
                "{shell:?}"
            );
        }
    }
}