1use crate::error::Result;
4use log::{debug, warn};
5use std::process::Command;
6use std::sync::Once;
7
8const TESTED_VERSION: &str = "2.1.232";
10
11pub fn tested_cli_version() -> &'static str {
17 TESTED_VERSION
18}
19
20static VERSION_CHECK: Once = Once::new();
22
23pub fn check_claude_version() -> Result<()> {
26 VERSION_CHECK.call_once(|| {
27 if let Err(e) = check_version_impl() {
28 debug!("Failed to check Claude CLI version: {}", e);
29 }
30 });
31 Ok(())
32}
33
34fn check_version_impl() -> Result<()> {
36 let mut command = Command::new("claude");
38 command.arg("--version");
39 crate::process::configure_no_window(&mut command);
40 let output = command.output().map_err(crate::error::Error::Io)?;
41
42 if !output.status.success() {
43 debug!("Failed to check Claude CLI version - command failed");
44 return Ok(());
45 }
46
47 let version_str = String::from_utf8_lossy(&output.stdout);
48 let version_line = version_str.lines().next().unwrap_or("");
49
50 if let Some(version) = version_line.split_whitespace().next() {
52 if is_version_newer(version, TESTED_VERSION) {
53 warn!(
54 "Claude CLI version {} is newer than tested version {}. \
55 Please report compatibility at: https://github.com/meawoppl/rust-claude-codes/pulls",
56 version, TESTED_VERSION
57 );
58 } else {
59 debug!(
60 "Claude CLI version {} is compatible (tested: {})",
61 version, TESTED_VERSION
62 );
63 }
64 } else {
65 warn!(
66 "Could not parse Claude CLI version from output: '{}'. \
67 Please report compatibility at: https://github.com/meawoppl/rust-claude-codes/pulls",
68 version_line
69 );
70 }
71
72 Ok(())
73}
74
75fn is_version_newer(version: &str, tested: &str) -> bool {
77 let v_parts: Vec<u32> = version.split('.').filter_map(|s| s.parse().ok()).collect();
78 let t_parts: Vec<u32> = tested.split('.').filter_map(|s| s.parse().ok()).collect();
79
80 use std::cmp::Ordering;
81
82 for i in 0..v_parts.len().min(t_parts.len()) {
83 match v_parts[i].cmp(&t_parts[i]) {
84 Ordering::Greater => return true,
85 Ordering::Less => return false,
86 Ordering::Equal => continue,
87 }
88 }
89
90 v_parts.len() > t_parts.len()
92}
93
94#[cfg(feature = "async-client")]
96pub async fn check_claude_version_async() -> Result<()> {
97 use tokio::sync::OnceCell;
98
99 static ASYNC_VERSION_CHECK: OnceCell<()> = OnceCell::const_new();
101
102 ASYNC_VERSION_CHECK
103 .get_or_init(|| async {
104 if let Err(e) = check_version_impl_async().await {
105 debug!("Failed to check Claude CLI version: {}", e);
106 }
107 })
108 .await;
109
110 Ok(())
111}
112
113#[cfg(feature = "async-client")]
115async fn check_version_impl_async() -> Result<()> {
116 use tokio::process::Command;
117
118 let mut command = Command::new("claude");
120 command.arg("--version");
121 crate::process::configure_no_window(command.as_std_mut());
122 let output = command.output().await.map_err(crate::error::Error::Io)?;
123
124 if !output.status.success() {
125 debug!("Failed to check Claude CLI version - command failed");
126 return Ok(());
127 }
128
129 let version_str = String::from_utf8_lossy(&output.stdout);
130 let version_line = version_str.lines().next().unwrap_or("");
131
132 if let Some(version) = version_line.split_whitespace().next() {
134 if is_version_newer(version, TESTED_VERSION) {
135 warn!(
136 "Claude CLI version {} is newer than tested version {}. \
137 Please report compatibility at: https://github.com/meawoppl/rust-claude-codes/pulls",
138 version, TESTED_VERSION
139 );
140 } else {
141 debug!(
142 "Claude CLI version {} is compatible (tested: {})",
143 version, TESTED_VERSION
144 );
145 }
146 } else {
147 warn!(
148 "Could not parse Claude CLI version from output: '{}'. \
149 Please report compatibility at: https://github.com/meawoppl/rust-claude-codes/pulls",
150 version_line
151 );
152 }
153
154 Ok(())
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn test_version_comparison() {
163 assert!(is_version_newer("1.0.90", "1.0.89"));
165 assert!(!is_version_newer("1.0.89", "1.0.90"));
166 assert!(!is_version_newer("1.0.89", "1.0.89"));
167
168 assert!(is_version_newer("1.1", "1.0.89"));
170 assert!(!is_version_newer("1.0", "1.0.89"));
171 assert!(is_version_newer("1.0.89.1", "1.0.89"));
172
173 assert!(is_version_newer("2.0.0", "1.99.99"));
175 assert!(!is_version_newer("0.9.99", "1.0.0"));
176 }
177}