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