Skip to main content

ai_agent/utils/plugins/
add_dir_plugin_settings.rs

1// Source: ~/claudecode/openclaudecode/src/utils/plugins/addDirPluginSettings.ts
2
3use serde::Deserialize;
4use std::collections::HashMap;
5use std::path::PathBuf;
6
7/// Extra known marketplace configuration.
8#[derive(Debug, Clone, Deserialize)]
9pub struct ExtraKnownMarketplace {
10    pub source: String,
11    pub repo: String,
12}
13
14/// Settings JSON structure.
15#[derive(Debug, Deserialize)]
16struct ParsedSettings {
17    #[serde(default)]
18    enabled_plugins: Option<HashMap<String, serde_json::Value>>,
19    #[serde(default)]
20    extra_known_marketplaces: Option<HashMap<String, ExtraKnownMarketplace>>,
21}
22
23const SETTINGS_FILES: &[&str] = &["settings.json", "settings.local.json"];
24
25/// Returns a merged record of enabled_plugins from all --add-dir directories.
26///
27/// Within each directory, settings.local.json is processed after settings.json
28/// (local wins within that dir). Across directories, later CLI-order wins on
29/// conflict.
30///
31/// This has the lowest priority -- callers must spread their standard settings
32/// on top to let user/project/local/flag/policy override.
33pub fn get_add_dir_enabled_plugins() -> HashMap<String, serde_json::Value> {
34    let mut result: HashMap<String, serde_json::Value> = HashMap::new();
35
36    for dir in get_additional_directories_for_claude_md() {
37        for file in SETTINGS_FILES {
38            let path = dir.join(".ai").join(file);
39            if let Ok(content) = std::fs::read_to_string(&path) {
40                if let Ok(parsed) = serde_json::from_str::<ParsedSettings>(&content) {
41                    if let Some(enabled_plugins) = parsed.enabled_plugins {
42                        result.extend(enabled_plugins);
43                    }
44                }
45            }
46        }
47    }
48
49    result
50}
51
52/// Returns a merged record of extra_known_marketplaces from all --add-dir directories.
53///
54/// Same priority rules as get_add_dir_enabled_plugins: settings.local.json wins
55/// within each dir, and callers spread standard settings on top.
56pub fn get_add_dir_extra_marketplaces() -> HashMap<String, ExtraKnownMarketplace> {
57    let mut result: HashMap<String, ExtraKnownMarketplace> = HashMap::new();
58
59    for dir in get_additional_directories_for_claude_md() {
60        for file in SETTINGS_FILES {
61            let path = dir.join(".ai").join(file);
62            if let Ok(content) = std::fs::read_to_string(&path) {
63                if let Ok(parsed) = serde_json::from_str::<ParsedSettings>(&content) {
64                    if let Some(extra_marketplaces) = parsed.extra_known_marketplaces {
65                        result.extend(extra_marketplaces);
66                    }
67                }
68            }
69        }
70    }
71
72    result
73}
74
75/// Get additional directories for AI config (localized from claudeMd).
76fn get_additional_directories_for_claude_md() -> Vec<PathBuf> {
77    // In production, this would read from the bootstrap state.
78    // For now, check environment variable.
79    if let Ok(dirs_str) = std::env::var("AI_CODE_ADDITIONAL_DIRS") {
80        dirs_str
81            .split('|')
82            .filter(|s| !s.is_empty())
83            .map(PathBuf::from)
84            .collect()
85    } else {
86        Vec::new()
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_empty_when_no_dirs() {
96        #[allow(unused_unsafe)]
97        unsafe {
98            std::env::remove_var("AI_CODE_ADDITIONAL_DIRS");
99        }
100        assert!(get_add_dir_enabled_plugins().is_empty());
101        assert!(get_add_dir_extra_marketplaces().is_empty());
102    }
103}