Skip to content

Commit 4429c23

Browse files
authored
Merge pull request #11 from alexx-ftw/fix/windows-codex-discovery
fix: improve Windows codex detection for npm global installations
2 parents d31267c + 98ad6b5 commit 4429c23

File tree

1 file changed

+59
-47
lines changed

1 file changed

+59
-47
lines changed

src-tauri/src/utils/codex_discovery.rs

Lines changed: 59 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ fn get_platform_binary_name() -> &'static str {
1414
}
1515

1616
pub fn discover_codex_command() -> Option<PathBuf> {
17-
let home = std::env::var("HOME").unwrap_or_default();
17+
let home = if cfg!(windows) {
18+
std::env::var("USERPROFILE").or_else(|_| std::env::var("HOME")).unwrap_or_default()
19+
} else {
20+
std::env::var("HOME").unwrap_or_default()
21+
};
1822
let binary_name = get_platform_binary_name();
1923

2024
// 0) Optional override via environment variable
@@ -31,83 +35,91 @@ pub fn discover_codex_command() -> Option<PathBuf> {
3135
// First priority: Check actual binary locations in node_modules
3236
let binary_locations = [
3337
// Bun global installation
34-
format!(
35-
"{}/.bun/install/global/node_modules/@openai/codex/bin/{}",
36-
home, binary_name
37-
),
38+
PathBuf::from(&home).join(".bun/install/global/node_modules/@openai/codex/bin").join(binary_name),
3839
// NPM rootless (user) global installation
39-
format!(
40-
"{}/.local/share/npm/lib/node_modules/@openai/codex/bin/{}",
41-
home, binary_name
42-
),
40+
PathBuf::from(&home).join(".local/share/npm/lib/node_modules/@openai/codex/bin").join(binary_name),
4341
// NPM global installations
44-
format!(
45-
"/usr/local/lib/node_modules/@openai/codex/bin/{}",
46-
binary_name
47-
),
48-
format!(
49-
"/opt/homebrew/lib/node_modules/@openai/codex/bin/{}",
50-
binary_name
51-
),
42+
PathBuf::from("/usr/local/lib/node_modules/@openai/codex/bin").join(binary_name),
43+
PathBuf::from("/opt/homebrew/lib/node_modules/@openai/codex/bin").join(binary_name),
5244
];
5345

54-
for path in &binary_locations {
55-
let path_buf = PathBuf::from(path);
46+
for path_buf in &binary_locations {
5647
if path_buf.exists() {
57-
log::debug!("Found codex binary at {}", path);
58-
return Some(path_buf);
48+
log::debug!("Found codex binary at {}", path_buf.display());
49+
return Some(path_buf.clone());
50+
}
51+
}
52+
53+
// Windows npm global installation paths
54+
if cfg!(windows) {
55+
if let Ok(appdata) = std::env::var("APPDATA") {
56+
let npm_paths = [
57+
PathBuf::from(&appdata).join("npm").join("codex.cmd"),
58+
PathBuf::from(&appdata).join("npm").join("codex.ps1"),
59+
PathBuf::from(&appdata).join("npm").join("codex"),
60+
];
61+
for path_buf in &npm_paths {
62+
if path_buf.exists() {
63+
log::debug!("Found npm codex at {}", path_buf.display());
64+
return Some(path_buf.clone());
65+
}
66+
}
5967
}
6068
}
6169

6270
// Second priority: Check if there are native rust/cargo installations
6371
let native_paths = [
64-
format!("{}/.cargo/bin/codex", home),
65-
"/usr/local/bin/codex".to_string(),
66-
"/opt/homebrew/bin/codex".to_string(),
72+
PathBuf::from(&home).join(".cargo/bin/codex"),
73+
PathBuf::from(&home).join(".cargo/bin/codex.exe"),
74+
PathBuf::from("/usr/local/bin/codex"),
75+
PathBuf::from("/opt/homebrew/bin/codex"),
6776
];
6877

69-
for path in &native_paths {
70-
let path_buf = PathBuf::from(path);
78+
for path_buf in &native_paths {
7179
if path_buf.exists() {
7280
// Check if it's a real binary (not a js wrapper)
73-
if let Ok(content) = std::fs::read_to_string(&path_buf) {
81+
if let Ok(content) = std::fs::read_to_string(path_buf) {
7482
if content.contains("codex.js") || content.starts_with("#!/usr/bin/env node") {
7583
// This is a wrapper script, skip it
76-
log::debug!("Skipping wrapper script at {}", path);
84+
log::debug!("Skipping wrapper script at {}", path_buf.display());
7785
continue;
7886
}
7987
}
80-
log::debug!("Found native codex binary at {}", path);
81-
return Some(path_buf);
88+
log::debug!("Found native codex binary at {}", path_buf.display());
89+
return Some(path_buf.clone());
8290
}
8391
}
8492

85-
// Final fallback: Check PATH for native binaries; prefer non-wrappers, but accept wrappers as last resort
8693
if let Ok(path_env) = std::env::var("PATH") {
8794
let separator = if cfg!(windows) { ';' } else { ':' };
8895
let mut wrapper_candidate: Option<PathBuf> = None;
96+
let candidate_names: &[&str] = if cfg!(windows) {
97+
&["codex.exe", "codex.cmd", "codex.ps1", "codex"]
98+
} else {
99+
&["codex"]
100+
};
89101
for dir in path_env.split(separator) {
90102
if dir.is_empty() {
91103
continue;
92104
}
93-
let exe_name = if cfg!(windows) { "codex.exe" } else { "codex" };
94-
let candidate = PathBuf::from(dir).join(exe_name);
95-
if candidate.exists() {
96-
// Try to verify if it's a wrapper
97-
if let Ok(content) = std::fs::read_to_string(&candidate) {
98-
let is_wrapper = content.contains("codex.js")
99-
|| content.starts_with("#!/usr/bin/env node")
100-
|| content.contains("import");
101-
if is_wrapper {
102-
// Keep as last-resort fallback
103-
wrapper_candidate = Some(candidate.clone());
104-
log::debug!("Found wrapper script candidate at {} (will use only if no native binary is found)", candidate.display());
105-
continue;
105+
for name in candidate_names {
106+
let candidate = PathBuf::from(dir).join(name);
107+
if candidate.exists() {
108+
if let Ok(content) = std::fs::read_to_string(&candidate) {
109+
let is_wrapper = content.contains("codex.js")
110+
|| content.starts_with("#!/usr/bin/env node")
111+
|| content.contains("import");
112+
if is_wrapper {
113+
if wrapper_candidate.is_none() {
114+
wrapper_candidate = Some(candidate.clone());
115+
log::debug!("Found wrapper script candidate at {} (will use only if no native binary is found)", candidate.display());
116+
}
117+
continue;
118+
}
106119
}
120+
log::debug!("Found codex in PATH at {}", candidate.display());
121+
return Some(candidate);
107122
}
108-
// Looks like a native binary
109-
log::debug!("Found native codex in PATH at {}", candidate.display());
110-
return Some(candidate);
111123
}
112124
}
113125
if let Some(wrapper) = wrapper_candidate {

0 commit comments

Comments
 (0)