use crate::error::Result;
use log::{debug, warn};
use std::process::Command;
use std::sync::Once;
const TESTED_VERSION: &str = "2.1.47";
static VERSION_CHECK: Once = Once::new();
pub fn check_claude_version() -> Result<()> {
VERSION_CHECK.call_once(|| {
if let Err(e) = check_version_impl() {
debug!("Failed to check Claude CLI version: {}", e);
}
});
Ok(())
}
fn check_version_impl() -> Result<()> {
let output = Command::new("claude")
.arg("--version")
.output()
.map_err(crate::error::Error::Io)?;
if !output.status.success() {
debug!("Failed to check Claude CLI version - command failed");
return Ok(());
}
let version_str = String::from_utf8_lossy(&output.stdout);
let version_line = version_str.lines().next().unwrap_or("");
if let Some(version) = version_line.split_whitespace().next() {
if is_version_newer(version, TESTED_VERSION) {
warn!(
"Claude CLI version {} is newer than tested version {}. \
Please report compatibility at: https://github.com/meawoppl/rust-claude-codes/pulls",
version, TESTED_VERSION
);
} else {
debug!(
"Claude CLI version {} is compatible (tested: {})",
version, TESTED_VERSION
);
}
} else {
warn!(
"Could not parse Claude CLI version from output: '{}'. \
Please report compatibility at: https://github.com/meawoppl/rust-claude-codes/pulls",
version_line
);
}
Ok(())
}
fn is_version_newer(version: &str, tested: &str) -> bool {
let v_parts: Vec<u32> = version.split('.').filter_map(|s| s.parse().ok()).collect();
let t_parts: Vec<u32> = tested.split('.').filter_map(|s| s.parse().ok()).collect();
use std::cmp::Ordering;
for i in 0..v_parts.len().min(t_parts.len()) {
match v_parts[i].cmp(&t_parts[i]) {
Ordering::Greater => return true,
Ordering::Less => return false,
Ordering::Equal => continue,
}
}
v_parts.len() > t_parts.len()
}
#[cfg(feature = "async-client")]
pub async fn check_claude_version_async() -> Result<()> {
use tokio::sync::OnceCell;
static ASYNC_VERSION_CHECK: OnceCell<()> = OnceCell::const_new();
ASYNC_VERSION_CHECK
.get_or_init(|| async {
if let Err(e) = check_version_impl_async().await {
debug!("Failed to check Claude CLI version: {}", e);
}
})
.await;
Ok(())
}
#[cfg(feature = "async-client")]
async fn check_version_impl_async() -> Result<()> {
use tokio::process::Command;
let output = Command::new("claude")
.arg("--version")
.output()
.await
.map_err(crate::error::Error::Io)?;
if !output.status.success() {
debug!("Failed to check Claude CLI version - command failed");
return Ok(());
}
let version_str = String::from_utf8_lossy(&output.stdout);
let version_line = version_str.lines().next().unwrap_or("");
if let Some(version) = version_line.split_whitespace().next() {
if is_version_newer(version, TESTED_VERSION) {
warn!(
"Claude CLI version {} is newer than tested version {}. \
Please report compatibility at: https://github.com/meawoppl/rust-claude-codes/pulls",
version, TESTED_VERSION
);
} else {
debug!(
"Claude CLI version {} is compatible (tested: {})",
version, TESTED_VERSION
);
}
} else {
warn!(
"Could not parse Claude CLI version from output: '{}'. \
Please report compatibility at: https://github.com/meawoppl/rust-claude-codes/pulls",
version_line
);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_comparison() {
assert!(is_version_newer("1.0.90", "1.0.89"));
assert!(!is_version_newer("1.0.89", "1.0.90"));
assert!(!is_version_newer("1.0.89", "1.0.89"));
assert!(is_version_newer("1.1", "1.0.89"));
assert!(!is_version_newer("1.0", "1.0.89"));
assert!(is_version_newer("1.0.89.1", "1.0.89"));
assert!(is_version_newer("2.0.0", "1.99.99"));
assert!(!is_version_newer("0.9.99", "1.0.0"));
}
}