claude_agent/agent/options/
cli.rs1use std::path::Path;
7
8use crate::auth::Auth;
9use crate::common::{IndexRegistry, Named, Provider};
10use crate::config::{Settings, SettingsLoader};
11use crate::context::{LeveledMemoryProvider, MemoryLoader, enterprise_base_path, user_base_path};
12use crate::hooks::CommandHook;
13use crate::output_style::file_output_style_provider;
14use crate::permissions::{PermissionMode, PermissionPolicy};
15use crate::skills::SkillIndexLoader;
16use crate::subagents::{SubagentIndexLoader, builtin_subagents};
17
18use super::builder::AgentBuilder;
19
20impl AgentBuilder {
21 pub async fn from_claude_code(mut self, path: impl AsRef<Path>) -> crate::Result<Self> {
42 let path = path.as_ref();
43 self = self.auth(Auth::ClaudeCli).await?;
44 self.config.working_dir = Some(path.to_path_buf());
45 Ok(self)
46 }
47
48 pub fn with_enterprise_resources(mut self) -> Self {
57 self.load_enterprise = true;
58 self
59 }
60
61 pub fn with_user_resources(mut self) -> Self {
68 self.load_user = true;
69 self
70 }
71
72 pub fn with_project_resources(mut self) -> Self {
80 self.load_project = true;
81 self
82 }
83
84 pub fn with_local_resources(mut self) -> Self {
93 self.load_local = true;
94 self
95 }
96
97 pub(super) async fn load_enterprise_resources(&mut self) {
102 let Some(base) = enterprise_base_path() else {
103 return;
104 };
105
106 self.load_settings_from(&base).await;
107 self.load_skills_from(&base).await;
108 self.load_subagents_from(&base).await;
109 self.load_output_styles_from(&base).await;
110 self.load_memory_from(&base).await;
111 }
112
113 pub(super) async fn load_user_resources(&mut self) {
114 let Some(base) = user_base_path() else {
115 return;
116 };
117
118 self.load_settings_from(&base).await;
119 self.load_skills_from(&base).await;
120 self.load_subagents_from(&base).await;
121 self.load_output_styles_from(&base).await;
122 self.load_memory_from(&base).await;
123 }
124
125 pub(super) async fn load_project_resources(&mut self) {
126 let Some(working_dir) = self.config.working_dir.clone() else {
127 tracing::warn!("working_dir not set, call from_claude_code() first");
128 return;
129 };
130
131 self.load_settings_from(&working_dir).await;
132 self.load_skills_from(&working_dir).await;
133 self.load_subagents_from(&working_dir).await;
134 self.load_output_styles_from(&working_dir).await;
135 self.load_memory_from(&working_dir).await;
136 }
137
138 pub(super) async fn load_local_resources(&mut self) {
139 let Some(working_dir) = self.config.working_dir.clone() else {
140 tracing::warn!("working_dir not set, call from_claude_code() first");
141 return;
142 };
143
144 let mut settings_loader = SettingsLoader::new();
145 if settings_loader.load_local(&working_dir).await.is_ok() {
146 let settings = settings_loader.into_settings();
147 self.apply_settings_mut(&settings);
148 }
149
150 let loader = MemoryLoader::new();
151 if let Ok(content) = loader.load_local(&working_dir).await
152 && !content.local_md.is_empty()
153 {
154 let provider = self
155 .memory_provider
156 .get_or_insert_with(LeveledMemoryProvider::new);
157 provider.add_memory_content(content);
158 }
159 }
160
161 async fn load_settings_from(&mut self, base: &Path) {
166 let mut loader = SettingsLoader::new();
167 if loader.load_from(base).await.is_ok() {
168 let settings = loader.into_settings();
169 self.apply_settings_mut(&settings);
170 }
171 }
172
173 async fn load_skills_from(&mut self, base: &Path) {
174 let skills_dir = base.join(".claude").join("skills");
175 let loader = SkillIndexLoader::new();
176 if let Ok(skills) = loader.scan_directory(&skills_dir).await {
177 let count = skills.len();
178 for skill in skills {
179 tracing::debug!(skill_name = %skill.name, "Registering skill to builder");
180 self.skill_registry
181 .get_or_insert_with(IndexRegistry::new)
182 .register(skill);
183 }
184 tracing::info!(skill_count = count, "Skills loaded from project");
185 }
186 }
187
188 async fn load_subagents_from(&mut self, base: &Path) {
189 let loader = SubagentIndexLoader::new();
190 let subagents_dir = base.join(".claude").join("subagents");
191 if let Ok(subagents) = loader.scan_directory(&subagents_dir).await {
192 for subagent in subagents {
193 self.subagent_registry
194 .get_or_insert_with(|| {
195 let mut registry = IndexRegistry::new();
196 registry.register_all(builtin_subagents());
197 registry
198 })
199 .register(subagent);
200 }
201 }
202 }
203
204 async fn load_output_styles_from(&mut self, base: &Path) {
205 let provider = file_output_style_provider().with_project_path(base);
206 if let Ok(styles) = provider.load_all().await
207 && let Some(first) = styles.first()
208 && self.output_style_name.is_none()
209 {
210 self.output_style_name = Some(first.name().to_string());
211 }
212 }
213
214 async fn load_memory_from(&mut self, base: &Path) {
215 let loader = MemoryLoader::new();
216 if let Ok(content) = loader.load_shared(base).await
217 && !content.is_empty()
218 {
219 let provider = self
220 .memory_provider
221 .get_or_insert_with(LeveledMemoryProvider::new);
222 provider.add_memory_content(content);
223 }
224 }
225
226 fn apply_settings_mut(&mut self, settings: &Settings) {
228 if let Some(model) = &settings.model {
229 self.config.model.primary = model.clone();
230 }
231 if let Some(small) = &settings.small_model {
232 self.config.model.small = small.clone();
233 }
234 if let Some(max_tokens) = settings.max_tokens {
235 self.config.model.max_tokens = max_tokens;
236 }
237
238 if let Some(ref hooks_settings) = settings.hooks {
239 for hook in CommandHook::from_settings(hooks_settings) {
240 self.hooks.register(hook);
241 }
242 }
243
244 self.config.security.env.extend(settings.env.clone());
245
246 if !settings.permissions.is_empty() {
247 let loaded_policy = settings.permissions.to_policy();
248 let existing_policy = std::mem::take(&mut self.config.security.permission_policy);
249 self.config.security.permission_policy =
250 Self::merge_permission_policies(existing_policy, loaded_policy);
251 }
252
253 if settings.sandbox.is_enabled() || settings.sandbox.has_network_settings() {
254 self.sandbox_settings = Some(settings.sandbox.clone());
255 }
256
257 if let Some(ref style_name) = settings.output_style {
258 self.output_style_name = Some(style_name.clone());
259 }
260
261 for (name, config_value) in &settings.mcp_servers {
262 if let Ok(config) = serde_json::from_value(config_value.clone()) {
263 self.mcp_configs.insert(name.clone(), config);
264 }
265 }
266
267 if !settings.tool_search.is_empty() && settings.tool_search.is_enabled() {
269 let context_window =
270 crate::types::context_window::for_model(&self.config.model.primary) as usize;
271 let config = settings.tool_search.to_config(context_window);
272 self.tool_search_config = Some(config);
273 }
274 }
275
276 #[cfg(test)]
278 pub(super) fn apply_settings(mut self, settings: Settings) -> Self {
279 self.apply_settings_mut(&settings);
280 self
281 }
282
283 fn merge_permission_policies(
284 from_settings: PermissionPolicy,
285 programmatic: PermissionPolicy,
286 ) -> PermissionPolicy {
287 let mode = if programmatic.mode != PermissionMode::Default {
288 programmatic.mode
289 } else {
290 from_settings.mode
291 };
292
293 let mut rules = from_settings.rules;
294 rules.extend(programmatic.rules);
295
296 let mut tool_limits = from_settings.tool_limits;
297 tool_limits.extend(programmatic.tool_limits);
298
299 PermissionPolicy {
300 mode,
301 rules,
302 tool_limits,
303 }
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310
311 #[test]
312 fn test_settings_apply_values() {
313 let settings = Settings {
314 model: Some("settings-model".to_string()),
315 small_model: Some("settings-small".to_string()),
316 max_tokens: Some(1000),
317 ..Default::default()
318 };
319
320 let builder = AgentBuilder::new().apply_settings(settings);
321
322 assert_eq!(builder.config.model.primary, "settings-model");
323 assert_eq!(builder.config.model.small, "settings-small");
324 assert_eq!(builder.config.model.max_tokens, 1000);
325 }
326
327 #[test]
328 fn test_explicit_config_after_settings_overrides() {
329 let settings = Settings {
330 model: Some("settings-model".to_string()),
331 small_model: Some("settings-small".to_string()),
332 max_tokens: Some(1000),
333 ..Default::default()
334 };
335
336 let builder = AgentBuilder::new()
337 .apply_settings(settings)
338 .model("explicit-model")
339 .small_model("explicit-small")
340 .max_tokens(2000);
341
342 assert_eq!(builder.config.model.primary, "explicit-model");
343 assert_eq!(builder.config.model.small, "explicit-small");
344 assert_eq!(builder.config.model.max_tokens, 2000);
345 }
346
347 #[test]
348 fn test_settings_cascade_order() {
349 let enterprise = Settings {
352 model: Some("enterprise-model".to_string()),
353 small_model: Some("enterprise-small".to_string()),
354 ..Default::default()
355 };
356 let user = Settings {
357 model: Some("user-model".to_string()),
358 ..Default::default()
359 };
360 let project = Settings {
361 small_model: Some("project-small".to_string()),
362 ..Default::default()
363 };
364
365 let builder = AgentBuilder::new()
366 .apply_settings(enterprise)
367 .apply_settings(user)
368 .apply_settings(project);
369
370 assert_eq!(builder.config.model.primary, "user-model");
372 assert_eq!(builder.config.model.small, "project-small");
374 }
375
376 #[test]
377 fn test_resource_flags_are_independent() {
378 let builder = AgentBuilder::new()
380 .with_enterprise_resources()
381 .with_user_resources()
382 .with_project_resources()
383 .with_local_resources();
384
385 assert!(builder.load_enterprise);
386 assert!(builder.load_user);
387 assert!(builder.load_project);
388 assert!(builder.load_local);
389
390 assert!(builder.memory_provider.is_none());
392 }
393
394 #[test]
395 fn test_chaining_order_does_not_affect_flags() {
396 let builder1 = AgentBuilder::new()
398 .with_enterprise_resources()
399 .with_user_resources()
400 .with_project_resources();
401
402 let builder2 = AgentBuilder::new()
403 .with_project_resources()
404 .with_user_resources()
405 .with_enterprise_resources();
406
407 assert_eq!(builder1.load_enterprise, builder2.load_enterprise);
408 assert_eq!(builder1.load_user, builder2.load_user);
409 assert_eq!(builder1.load_project, builder2.load_project);
410 }
411}