1mod legacy;
11mod load;
12mod runtime;
13mod schema;
14mod validate;
15
16pub use legacy::Deprecated;
17pub use load::{Setting, read_layer};
18pub use schema::*;
19
20use anyhow::Result;
21use scv_client::Layout;
22
23#[derive(Debug, Clone)]
26pub(crate) struct Instance {
27 pub(crate) layout: Layout,
28 pub(crate) overrides: ConfigOverrides,
29}
30
31impl Instance {
32 fn files(&self) -> ConfigOverrides {
35 ConfigOverrides {
36 config_file: self.overrides.config_file.clone(),
37 ..ConfigOverrides::default()
38 }
39 }
40
41 pub(crate) fn load(&self, workspace: &std::path::Path) -> Result<Config> {
43 Config::load(&self.layout, workspace, self.files())
44 }
45
46 pub(crate) fn load_user(&self) -> Result<Config> {
48 Config::load_user(&self.layout, self.files())
49 }
50}
51
52fn claude_warnings_for_layout(layout: &Layout) -> Vec<String> {
54 let mut warnings = Vec::new();
55 let agents = std::fs::read_to_string(layout.config())
56 .ok()
57 .and_then(|text| text.parse::<toml::Value>().ok())
58 .and_then(|value| value.get("agents").cloned())
59 .and_then(|value| value.as_table().cloned());
60 let explicit_claude = agents
61 .as_ref()
62 .is_some_and(|agents| agents.contains_key("claude"));
63 let explicit_claude_ai = agents
64 .as_ref()
65 .is_some_and(|agents| agents.contains_key("claude-ai"));
66 if explicit_claude
67 && !explicit_claude_ai
68 && scv_tools::stores::claude_status(
69 scv_tools::adapters::AgentAuth::ClaudeSubscription,
70 &layout.agent_home("claude"),
71 )
72 .is_ok_and(|status| status.ready)
73 {
74 warnings.push(
75 "claude now bills an API key and claude-ai uses the Claude subscription. agents/claude holds a Claude subscription login, so claude will not run. To keep using the subscription, move agents/claude to agents/claude-ai and [agents.claude] to [agents.claude-ai] (see docs/configuration.md).".into(),
76 );
77 }
78 if std::path::Path::new("/etc/claude-code/managed-settings.json").is_file() {
79 warnings.push(
80 "machine policy /etc/claude-code/managed-settings.json outranks SCV's pinned Claude Code credentials for claude and claude-ai".into(),
81 );
82 }
83 warnings
84}
85
86impl Config {
87 pub fn claude_warnings(&self) -> Vec<String> {
88 claude_warnings_for_layout(&Layout::new(&self.instance_home))
89 }
90}
91
92impl Instance {
93 pub(crate) fn claude_warnings(&self) -> Vec<String> {
94 claude_warnings_for_layout(&self.layout)
95 }
96
97 pub(crate) fn log_policy(&self) {
101 let config = match Config::load_user(&self.layout, self.overrides.clone()) {
102 Ok(config) => config,
103 Err(error) => {
104 tracing::warn!("Cannot read the policy mode: {error:#}");
105 return;
106 }
107 };
108 let agents: Vec<String> = config
109 .agent_modes()
110 .into_iter()
111 .map(|(agent, mode)| format!("{agent} {mode}"))
112 .collect();
113 if agents.is_empty() {
114 tracing::info!("Policy mode {}", config.policy_mode());
115 } else {
116 tracing::info!(
117 "Policy mode {}; agents with their own: {}",
118 config.policy_mode(),
119 agents.join(", ")
120 );
121 }
122 for deprecated in config.deprecated() {
123 tracing::warn!("{}", deprecated.warning());
124 }
125 }
126}
127
128#[cfg(test)]
129mod tests;