Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions apps/codex-plus-manager/src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1450,9 +1450,11 @@ pub fn find_desktop_codex_cli() -> CommandResult<Value> {
) else {
return failed("未找到 Codex Desktop 应用。", json!({ "path": null }));
};
let Some(path) = codex_plus_core::app_paths::find_bundled_codex_cli(&app_dir) else {
let bundled = codex_plus_core::app_paths::find_bundled_codex_cli(&app_dir);
let standalone = codex_plus_core::app_paths::find_standalone_codex_cli();
let Some(path) = [bundled, standalone].into_iter().flatten().find(|candidate| codex_cli_can_start(candidate)) else {
return failed(
"已找到 Codex Desktop,但包内没有可用的 Codex CLI。",
"已找到 Codex Desktop,但没有可用的 Codex CLI;请安装或指定用户目录中的 Codex CLI。",
json!({ "path": null }),
);
};
Expand All @@ -1462,6 +1464,27 @@ pub fn find_desktop_codex_cli() -> CommandResult<Value> {
)
}

fn codex_cli_can_start(path: &std::path::Path) -> bool {
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
let mut command = Command::new(path);
command.arg("--version").stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
command.creation_flags(codex_plus_core::windows_create_no_window());
}
let Ok(mut child) = command.spawn() else { return false; };
let deadline = Instant::now() + Duration::from_secs(5);
loop {
match child.try_wait() {
Ok(Some(status)) => return status.success(),
Ok(None) if Instant::now() < deadline => std::thread::sleep(Duration::from_millis(25)),
_ => { let _ = child.kill(); let _ = child.wait(); return false; }
}
}
}

fn spawn_weixin_connect(
settings: BackendSettings,
) -> anyhow::Result<codex_plus_core::connect::WeixinConnectStatus> {
Expand Down
56 changes: 56 additions & 0 deletions crates/codex-plus-core/src/app_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,62 @@ pub fn find_standalone_codex_app_dir() -> Option<PathBuf> {
None
}

/// Finds the CLI shipped by the standalone Codex installer.
pub fn find_standalone_codex_cli() -> Option<PathBuf> {
let local_appdata = std::env::var_os("LOCALAPPDATA")?;
find_standalone_codex_cli_in(&PathBuf::from(local_appdata).join("OpenAI").join("Codex").join("bin"))
}

fn find_standalone_codex_cli_in(bin_dir: &Path) -> Option<PathBuf> {
let mut candidates = Vec::new();
if let Some(path) = standalone_cli_in_dir(bin_dir) { candidates.push(path); }
if let Ok(entries) = std::fs::read_dir(bin_dir) {
for entry in entries.flatten() {
if entry.path().is_dir() {
if let Some(path) = standalone_cli_in_dir(&entry.path()) { candidates.push(path); }
}
}
}
candidates.sort_by_key(|path| std::fs::metadata(path).and_then(|metadata| metadata.modified()).ok());
candidates.pop()
}

fn standalone_cli_in_dir(dir: &Path) -> Option<PathBuf> {
["codex.exe", "codex", "Codex.exe", "Codex"].iter().map(|name| dir.join(name)).find(|path| path.is_file())
}

#[cfg(test)]
mod standalone_cli_tests {
use super::find_standalone_codex_cli_in;

#[test]
fn standalone_cli_finds_latest_versioned_binary() {
let temp = tempfile::tempdir().unwrap();
let old = temp.path().join("old");
let new = temp.path().join("new");
std::fs::create_dir_all(&old).unwrap();
std::fs::create_dir_all(&new).unwrap();
std::fs::write(old.join("codex.exe"), "old").unwrap();
std::fs::write(new.join("codex.exe"), "new").unwrap();
std::fs::File::options().write(true).open(old.join("codex.exe")).unwrap().set_times(std::fs::FileTimes::new().set_modified(std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_000_000))).unwrap();
assert_eq!(find_standalone_codex_cli_in(temp.path()), Some(new.join("codex.exe")));
}

#[test]
fn standalone_cli_returns_none_without_binary() {
let temp = tempfile::tempdir().unwrap();
assert_eq!(find_standalone_codex_cli_in(temp.path()), None);
}

#[test]
fn standalone_cli_finds_unversioned_binary() {
let temp = tempfile::tempdir().unwrap();
let binary = temp.path().join("codex.exe");
std::fs::write(&binary, "cli").unwrap();
assert_eq!(find_standalone_codex_cli_in(temp.path()), Some(binary));
}
}

pub fn resolve_codex_app_dir_with_saved(
app_dir: Option<&Path>,
saved_app_path: Option<&str>,
Expand Down
Loading