fix: detect linuxbrew ai agent binaries
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use crate::ai_agents::{AiAgentPermissionMode, AiAgentStreamEvent};
|
||||
use serde::Deserialize;
|
||||
use std::io::BufRead;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, ExitStatus};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
@@ -47,6 +47,51 @@ pub(crate) fn version_for_binary(binary: &PathBuf) -> Option<String> {
|
||||
.map(|output| String::from_utf8_lossy(&output.stdout).trim().to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn find_executable_binary_candidate(
|
||||
candidates: Vec<PathBuf>,
|
||||
agent_label: &str,
|
||||
) -> Result<Option<PathBuf>, String> {
|
||||
let mut first_unusable_candidate = None;
|
||||
|
||||
for candidate in candidates {
|
||||
if !candidate.exists() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if is_executable_file(&candidate) {
|
||||
return Ok(Some(candidate));
|
||||
}
|
||||
|
||||
if first_unusable_candidate.is_none() {
|
||||
first_unusable_candidate = Some(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
match first_unusable_candidate {
|
||||
Some(candidate) => Err(format!(
|
||||
"{agent_label} binary found at {} but it is not executable. Fix the file permissions or reinstall the CLI.",
|
||||
candidate.display()
|
||||
)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_executable_file(path: &Path) -> bool {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
std::fs::metadata(path)
|
||||
.map(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
path.is_file()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_json_line(
|
||||
line: Result<String, std::io::Error>,
|
||||
) -> Result<Option<serde_json::Value>, String> {
|
||||
@@ -183,4 +228,42 @@ mod tests {
|
||||
let error = parse_json_line(Err(std::io::Error::other("broken pipe"))).unwrap_err();
|
||||
assert!(error.contains("broken pipe"));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn executable_binary_candidate_skips_unusable_file_when_later_candidate_works() {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let unusable = dir.path().join("codex-unusable");
|
||||
let executable = dir.path().join("codex");
|
||||
std::fs::write(&unusable, "#!/bin/sh\n").unwrap();
|
||||
std::fs::write(&executable, "#!/bin/sh\n").unwrap();
|
||||
std::fs::set_permissions(&unusable, std::fs::Permissions::from_mode(0o644)).unwrap();
|
||||
std::fs::set_permissions(&executable, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
|
||||
let found =
|
||||
find_executable_binary_candidate(vec![unusable, executable.clone()], "Codex CLI")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(found, Some(executable));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn executable_binary_candidate_reports_unusable_file_when_no_candidate_works() {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let unusable = dir.path().join("opencode");
|
||||
std::fs::write(&unusable, "#!/bin/sh\n").unwrap();
|
||||
std::fs::set_permissions(&unusable, std::fs::Permissions::from_mode(0o644)).unwrap();
|
||||
|
||||
let error =
|
||||
find_executable_binary_candidate(vec![unusable.clone()], "OpenCode CLI").unwrap_err();
|
||||
|
||||
assert!(error.contains("OpenCode CLI binary found"));
|
||||
assert!(error.contains(&unusable.display().to_string()));
|
||||
assert!(error.contains("not executable"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user