claude_code/builder/
mod.rs1use std::{collections::BTreeMap, path::PathBuf, time::Duration};
2
3use crate::client::ClaudeClient;
4use crate::home::{ClaudeHomeLayout, ClaudeHomeSeedLevel, ClaudeHomeSeedRequest};
5
6#[derive(Debug, Clone)]
7pub struct ClaudeClientBuilder {
8 pub(crate) binary: Option<PathBuf>,
9 pub(crate) working_dir: Option<PathBuf>,
10 pub(crate) env: BTreeMap<String, String>,
11 pub(crate) claude_home: Option<PathBuf>,
12 pub(crate) create_home_dirs: bool,
13 pub(crate) home_seed: Option<ClaudeHomeSeedRequest>,
14 pub(crate) timeout: Option<Duration>,
15 pub(crate) mirror_stdout: bool,
16 pub(crate) mirror_stderr: bool,
17}
18
19impl Default for ClaudeClientBuilder {
20 fn default() -> Self {
21 Self {
22 binary: None,
23 working_dir: None,
24 env: BTreeMap::new(),
25 claude_home: None,
26 create_home_dirs: true,
27 home_seed: None,
28 timeout: Some(Duration::from_secs(120)),
29 mirror_stdout: false,
30 mirror_stderr: false,
31 }
32 }
33}
34
35impl ClaudeClientBuilder {
36 pub fn binary(mut self, binary: impl Into<PathBuf>) -> Self {
37 self.binary = Some(binary.into());
38 self
39 }
40
41 pub fn working_dir(mut self, dir: impl Into<PathBuf>) -> Self {
42 self.working_dir = Some(dir.into());
43 self
44 }
45
46 pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
47 self.env.insert(key.into(), value.into());
48 self
49 }
50
51 pub fn claude_home(mut self, home: impl Into<PathBuf>) -> Self {
58 self.claude_home = Some(home.into());
59 self
60 }
61
62 pub fn create_home_dirs(mut self, enable: bool) -> Self {
65 self.create_home_dirs = enable;
66 self
67 }
68
69 pub fn seed_profile_from(
74 mut self,
75 seed_user_home: impl Into<PathBuf>,
76 level: ClaudeHomeSeedLevel,
77 ) -> Self {
78 self.home_seed = Some(ClaudeHomeSeedRequest {
79 seed_user_home: seed_user_home.into(),
80 level,
81 });
82 self
83 }
84
85 pub fn seed_profile_from_current_user_home(mut self, level: ClaudeHomeSeedLevel) -> Self {
89 let home = std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE"));
90 if let Some(home) = home {
91 self.home_seed = Some(ClaudeHomeSeedRequest {
92 seed_user_home: PathBuf::from(home),
93 level,
94 });
95 }
96 self
97 }
98
99 pub fn timeout(mut self, timeout: Option<Duration>) -> Self {
100 self.timeout = timeout;
101 self
102 }
103
104 pub fn mirror_stdout(mut self, enabled: bool) -> Self {
105 self.mirror_stdout = enabled;
106 self
107 }
108
109 pub fn mirror_stderr(mut self, enabled: bool) -> Self {
110 self.mirror_stderr = enabled;
111 self
112 }
113
114 pub fn build(mut self) -> ClaudeClient {
115 self.env
117 .entry("DISABLE_AUTOUPDATER".to_string())
118 .or_insert_with(|| "1".to_string());
119
120 let claude_home_path = self
121 .claude_home
122 .take()
123 .or_else(|| std::env::var_os("CLAUDE_HOME").map(PathBuf::from));
124 let claude_home = claude_home_path.map(ClaudeHomeLayout::new);
125
126 if let Some(layout) = claude_home.as_ref() {
127 let root = layout.root().to_string_lossy().to_string();
128 self.env
129 .entry("CLAUDE_HOME".to_string())
130 .or_insert(root.clone());
131 self.env.entry("HOME".to_string()).or_insert(root.clone());
132 self.env
133 .entry("XDG_CONFIG_HOME".to_string())
134 .or_insert(layout.xdg_config_home().to_string_lossy().to_string());
135 self.env
136 .entry("XDG_DATA_HOME".to_string())
137 .or_insert(layout.xdg_data_home().to_string_lossy().to_string());
138 self.env
139 .entry("XDG_CACHE_HOME".to_string())
140 .or_insert(layout.xdg_cache_home().to_string_lossy().to_string());
141
142 #[cfg(windows)]
143 {
144 self.env
145 .entry("USERPROFILE".to_string())
146 .or_insert(root.clone());
147 self.env
148 .entry("APPDATA".to_string())
149 .or_insert(layout.appdata_dir().to_string_lossy().to_string());
150 self.env
151 .entry("LOCALAPPDATA".to_string())
152 .or_insert(layout.localappdata_dir().to_string_lossy().to_string());
153 }
154 }
155
156 let home_materialize_status = std::sync::Arc::new(std::sync::OnceLock::new());
157 let home_seed_status = std::sync::Arc::new(std::sync::OnceLock::new());
158
159 if let Some(layout) = claude_home.as_ref() {
160 let res = layout
161 .materialize(self.create_home_dirs)
162 .map_err(|e| e.to_string());
163 let _ = home_materialize_status.set(res);
164
165 if let Some(seed_req) = self.home_seed.as_ref() {
166 let res = layout
167 .seed_from_user_home(&seed_req.seed_user_home, seed_req.level)
168 .map(|_| ())
169 .map_err(|e| e.to_string());
170 let _ = home_seed_status.set(res);
171 }
172 }
173
174 ClaudeClient {
175 binary: self.binary,
176 working_dir: self.working_dir,
177 env: self.env,
178 claude_home,
179 create_home_dirs: self.create_home_dirs,
180 home_seed: self.home_seed,
181 home_materialize_status,
182 home_seed_status,
183 timeout: self.timeout,
184 mirror_stdout: self.mirror_stdout,
185 mirror_stderr: self.mirror_stderr,
186 }
187 }
188}