diff --git a/.codescene-thresholds b/.codescene-thresholds index 6828037b..1e4a530c 100644 --- a/.codescene-thresholds +++ b/.codescene-thresholds @@ -1,2 +1,2 @@ HOTSPOT_THRESHOLD=10.0 -AVERAGE_THRESHOLD=9.94 +AVERAGE_THRESHOLD=9.95 diff --git a/src-tauri/src/opencode_discovery.rs b/src-tauri/src/opencode_discovery.rs index 4ce69bd5..068178f6 100644 --- a/src-tauri/src/opencode_discovery.rs +++ b/src-tauri/src/opencode_discovery.rs @@ -86,14 +86,25 @@ fn path_from_successful_output(output: &std::process::Output) -> Option } fn first_existing_path(stdout: &str) -> Option { - stdout.lines().find_map(|line| { - let trimmed = line.trim(); - if trimmed.is_empty() { - return None; - } - let candidate = PathBuf::from(trimmed); - candidate.exists().then_some(candidate) - }) + first_existing_path_for_platform(stdout, cfg!(windows)) +} + +fn first_existing_path_for_platform(stdout: &str, windows: bool) -> Option { + let mut paths = stdout.lines().filter_map(existing_path); + if windows { + return paths.find(|path| crate::cli_agent_runtime::has_windows_cli_extension(path)); + } + + paths.next() +} + +fn existing_path(line: &str) -> Option { + let trimmed = line.trim(); + if trimmed.is_empty() { + return None; + } + let candidate = PathBuf::from(trimmed); + candidate.exists().then_some(candidate) } fn opencode_binary_candidates() -> Vec { @@ -105,12 +116,16 @@ fn opencode_binary_candidates() -> Vec { fn opencode_binary_candidates_for_home(home: &Path) -> Vec { vec![ home.join(".local/bin/opencode"), + home.join(".local/bin/opencode.cmd"), home.join(".local/bin/opencode.exe"), home.join(".opencode/bin/opencode"), + home.join(".opencode/bin/opencode.cmd"), home.join(".opencode/bin/opencode.exe"), home.join(".local/share/mise/shims/opencode"), + home.join(".local/share/mise/shims/opencode.cmd"), home.join(".local/share/mise/shims/opencode.exe"), home.join(".asdf/shims/opencode"), + home.join(".asdf/shims/opencode.cmd"), home.join(".asdf/shims/opencode.exe"), home.join(".npm-global/bin/opencode"), home.join(".npm-global/bin/opencode.cmd"), @@ -119,12 +134,14 @@ fn opencode_binary_candidates_for_home(home: &Path) -> Vec { home.join(".npm/bin/opencode.cmd"), home.join(".npm/bin/opencode.exe"), home.join(".bun/bin/opencode"), + home.join(".bun/bin/opencode.cmd"), home.join(".bun/bin/opencode.exe"), home.join(".linuxbrew/bin/opencode"), home.join("AppData/Roaming/npm/opencode.cmd"), home.join("AppData/Roaming/npm/opencode.exe"), home.join("AppData/Local/pnpm/opencode.cmd"), home.join("AppData/Local/pnpm/opencode.exe"), + home.join("scoop/shims/opencode.cmd"), home.join("scoop/shims/opencode.exe"), PathBuf::from("/home/linuxbrew/.linuxbrew/bin/opencode"), PathBuf::from("/usr/local/bin/opencode"), @@ -182,14 +199,20 @@ mod tests { let home = PathBuf::from(r"C:\Users\alex"); let candidates = opencode_binary_candidates_for_home(&home); let expected = [ + home.join(".local/bin/opencode.cmd"), + home.join(".opencode/bin/opencode.cmd"), + home.join(".local/share/mise/shims/opencode.cmd"), + home.join(".asdf/shims/opencode.cmd"), home.join(".npm-global/bin/opencode.cmd"), home.join(".npm-global/bin/opencode.exe"), home.join(".npm/bin/opencode.cmd"), home.join(".npm/bin/opencode.exe"), + home.join(".bun/bin/opencode.cmd"), home.join("AppData/Roaming/npm/opencode.cmd"), home.join("AppData/Roaming/npm/opencode.exe"), home.join("AppData/Local/pnpm/opencode.cmd"), home.join("AppData/Local/pnpm/opencode.exe"), + home.join("scoop/shims/opencode.cmd"), home.join("scoop/shims/opencode.exe"), ]; @@ -220,4 +243,20 @@ mod tests { assert_eq!(first_existing_path(&stdout), Some(opencode)); } + + #[test] + fn windows_path_lookup_prefers_cmd_shim_over_extensionless_npm_script() { + let dir = tempfile::tempdir().unwrap(); + let shell_script = dir.path().join("opencode"); + let cmd_shim = dir.path().join("opencode.cmd"); + std::fs::write(&shell_script, "#!/bin/sh\n").unwrap(); + std::fs::write(&cmd_shim, "@ECHO off\n").unwrap(); + + let stdout = format!("{}\n{}\n", shell_script.display(), cmd_shim.display()); + + assert_eq!( + first_existing_path_for_platform(&stdout, true), + Some(cmd_shim) + ); + } }