Skip to main content

bamboo_server/claude_runner/
mod.rs

1//! Claude Code binary discovery and management
2
3mod command;
4mod discovery;
5mod runner;
6mod stream_json;
7mod version;
8
9use serde::{Deserialize, Serialize};
10use std::cmp::Ordering;
11use tracing::{info, warn};
12
13use discovery::discover_system_installations;
14use version::{compare_versions, source_preference};
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
17pub enum InstallationType {
18    System,
19    Custom,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct ClaudeInstallation {
24    pub path: String,
25    pub version: Option<String>,
26    pub source: String,
27    pub installation_type: InstallationType,
28}
29
30/// Find the best Claude binary installation
31pub fn find_claude_binary() -> Result<String, String> {
32    info!("Searching for claude binary...");
33
34    let installations = discover_system_installations();
35
36    if installations.is_empty() {
37        warn!("Could not find claude binary in any location");
38        return Err("Claude Code not found. Please ensure it's installed in one of these locations: PATH, /usr/local/bin, /opt/homebrew/bin, ~/.nvm/versions/node/*/bin, ~/.claude/local, ~/.local/bin".to_string());
39    }
40
41    for installation in &installations {
42        info!("Found Claude installation: {:?}", installation);
43    }
44
45    if let Some(best) = select_best_installation(installations) {
46        info!(
47            "Selected Claude installation: path={}, version={:?}, source={}",
48            best.path, best.version, best.source
49        );
50        Ok(best.path)
51    } else {
52        Err("No valid Claude installation found".to_string())
53    }
54}
55
56/// Best-effort Claude Code binary discovery.
57pub fn try_find_claude_binary() -> Option<String> {
58    find_claude_binary().ok()
59}
60
61/// Discover all Claude installations on the system
62pub fn discover_claude_installations() -> Vec<ClaudeInstallation> {
63    info!("Discovering all Claude installations...");
64
65    let mut installations = discover_system_installations();
66
67    installations.sort_by(|a, b| match (&a.version, &b.version) {
68        (Some(v1), Some(v2)) => match compare_versions(v2, v1) {
69            Ordering::Equal => source_preference(a).cmp(&source_preference(b)),
70            other => other,
71        },
72        (Some(_), None) => Ordering::Less,
73        (None, Some(_)) => Ordering::Greater,
74        (None, None) => source_preference(a).cmp(&source_preference(b)),
75    });
76
77    installations
78}
79
80fn select_best_installation(installations: Vec<ClaudeInstallation>) -> Option<ClaudeInstallation> {
81    installations
82        .into_iter()
83        .max_by(|a, b| match (&a.version, &b.version) {
84            (Some(v1), Some(v2)) => compare_versions(v1, v2),
85            (Some(_), None) => Ordering::Greater,
86            (None, Some(_)) => Ordering::Less,
87            (None, None) => {
88                if a.path == "claude" && b.path != "claude" {
89                    Ordering::Less
90                } else if a.path != "claude" && b.path == "claude" {
91                    Ordering::Greater
92                } else {
93                    Ordering::Equal
94                }
95            }
96        })
97}
98
99pub use command::create_command_with_env;
100pub use command::create_tokio_command_with_env;
101pub use runner::{spawn_claude_code_cli, ClaudeCodeCliConfig};