bamboo_server/claude_runner/
mod.rs1mod 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
30pub 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
56pub fn try_find_claude_binary() -> Option<String> {
58 find_claude_binary().ok()
59}
60
61pub 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};