Skip to main content

acp_cli/
config.rs

1use crate::agent::registry::AgentOverride;
2use crate::client::permissions::PermissionMode;
3use crate::session::scoping::find_git_root;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::path::Path;
7
8#[derive(Debug, Deserialize, Serialize, Default, Clone)]
9pub struct AcpCliConfig {
10    pub default_agent: Option<String>,
11    pub default_permissions: Option<PermissionMode>,
12    pub timeout: Option<u64>,
13    pub format: Option<String>,
14    pub agents: Option<HashMap<String, AgentOverride>>,
15    /// Anthropic auth token for Claude agent authentication.
16    /// Set via `acp-cli init` or manually in `~/.acp-cli/config.json`.
17    pub auth_token: Option<String>,
18}
19
20impl AcpCliConfig {
21    /// Load the global config from `~/.acp-cli/config.json`.
22    pub fn load() -> Self {
23        dirs::home_dir()
24            .map(|h| h.join(".acp-cli").join("config.json"))
25            .map(Self::load_from)
26            .unwrap_or_default()
27    }
28
29    /// Load a project-level config by walking from `cwd` up to the git root
30    /// and reading `.acp-cli.json` there. Returns `Default` if not found.
31    pub fn load_project(cwd: &Path) -> Self {
32        find_git_root(cwd)
33            .map(|root| root.join(".acp-cli.json"))
34            .map(Self::load_from)
35            .unwrap_or_default()
36    }
37
38    pub fn load_from(path: impl AsRef<Path>) -> Self {
39        std::fs::read_to_string(path.as_ref())
40            .ok()
41            .and_then(|s| serde_json::from_str(&s).ok())
42            .unwrap_or_default()
43    }
44
45    /// Merge project config on top of global. Project wins for non-None fields.
46    pub fn merge(self, project: AcpCliConfig) -> AcpCliConfig {
47        AcpCliConfig {
48            default_agent: project.default_agent.or(self.default_agent),
49            default_permissions: project.default_permissions.or(self.default_permissions),
50            timeout: project.timeout.or(self.timeout),
51            format: project.format.or(self.format),
52            agents: match (self.agents, project.agents) {
53                (Some(mut base), Some(proj)) => {
54                    base.extend(proj);
55                    Some(base)
56                }
57                (base, proj) => proj.or(base),
58            },
59            auth_token: project.auth_token.or(self.auth_token),
60        }
61    }
62}