sqlite_graphrag/commands/ingest_codex/
binary.rs1use super::types::MIN_CODEX_VERSION;
4use crate::errors::AppError;
5use std::path::{Path, PathBuf};
6use std::process::{Command, Stdio};
7
8pub fn find_codex_binary(explicit: Option<&Path>) -> Result<PathBuf, AppError> {
10 if let Some(p) = explicit {
11 if p.exists() {
12 return Ok(p.to_path_buf());
13 }
14 return Err(AppError::Validation(
15 crate::i18n::validation::binary_not_found_at_path(
16 "Codex CLI",
17 &p.display().to_string(),
18 ),
19 ));
20 }
21
22 if let Some(env_path) = crate::runtime_config::codex_binary() {
23 let p = PathBuf::from(&env_path);
24 if p.exists() {
25 return Ok(p);
26 }
27 }
28
29 let name = if cfg!(windows) { "codex.exe" } else { "codex" };
30 if let Some(path_var) = std::env::var_os("PATH") {
31 for dir in std::env::split_paths(&path_var) {
32 let candidate = dir.join(name);
33 if candidate.exists() {
34 return Ok(candidate);
35 }
36 }
37 }
38
39 Err(AppError::Validation(
40 "Codex CLI binary not found in PATH. Install it from https://github.com/openai/codex or specify --codex-binary".to_string(),
41 ))
42}
43
44pub(crate) fn validate_codex_version(binary: &Path) -> Result<String, AppError> {
51 let resolved = which::which(binary).map_err(|_| {
52 AppError::Validation(crate::i18n::validation::executable_not_in_path(
53 &binary.display().to_string(),
54 ))
55 })?;
56 let output = Command::new(&resolved)
57 .arg("--version")
58 .stdin(Stdio::null())
59 .stdout(Stdio::piped())
60 .stderr(Stdio::piped())
61 .output()
62 .map_err(AppError::Io)?;
63
64 let raw = String::from_utf8(output.stdout)
65 .map_err(|_| AppError::Validation(crate::i18n::validation::codex_version_not_utf8()))?;
66
67 let version_str = raw.trim().to_string();
68
69 let numeric = version_str.split_whitespace().last().unwrap_or("").trim();
71
72 fn parse_semver(s: &str) -> Option<(u64, u64, u64)> {
73 let parts: Vec<&str> = s.splitn(3, '.').collect();
74 if parts.len() < 2 {
75 return None;
76 }
77 let major = parts[0].parse::<u64>().ok()?;
78 let minor = parts[1].parse::<u64>().ok()?;
79 let patch = parts
80 .get(2)
81 .and_then(|p| p.parse::<u64>().ok())
82 .unwrap_or(0);
83 Some((major, minor, patch))
84 }
85
86 if let (Some(actual), Some(min)) = (parse_semver(numeric), parse_semver(MIN_CODEX_VERSION)) {
87 if actual < min {
88 return Err(AppError::Validation(
89 crate::i18n::validation::version_below_minimum(
90 "Codex CLI",
91 numeric,
92 MIN_CODEX_VERSION,
93 ),
94 ));
95 }
96 }
97
98 Ok(version_str)
99}