Skip to main content

bamboo_agent/claude/
mod.rs

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