Skip to main content

ai_agent/utils/plugins/
plugin_directories.rs

1// Source: ~/claudecode/openclaudecode/src/utils/plugins/pluginDirectories.ts
2#![allow(dead_code)]
3
4use std::path::PathBuf;
5
6use once_cell::sync::OnceCell;
7
8const PLUGINS_DIR: &str = "plugins";
9const COWORK_PLUGINS_DIR: &str = "cowork_plugins";
10
11static PLUGINS_DIR_NAME: OnceCell<String> = OnceCell::new();
12
13/// Get the plugins directory name based on current mode.
14fn get_plugins_dir_name() -> &'static str {
15    PLUGINS_DIR_NAME.get_or_init(|| {
16        // Stub: bootstrap::state module not available
17        // Check env var for cowork plugins
18        if std::env::var("CLAUDE_CODE_USE_COWORK_PLUGINS")
19            .map(|v| v == "1" || v.to_lowercase() == "true")
20            .unwrap_or(false)
21        {
22            return COWORK_PLUGINS_DIR.to_string();
23        }
24        PLUGINS_DIR.to_string()
25    })
26}
27
28/// Get the full path to the plugins directory.
29pub fn get_plugins_directory() -> String {
30    // Check for env override
31    if let Ok(env_override) = std::env::var("CLAUDE_CODE_PLUGIN_CACHE_DIR") {
32        return expand_tilde(&env_override);
33    }
34
35    let config_home = crate::utils::env_utils::get_claude_config_home_dir();
36    format!("{}/{}", config_home, get_plugins_dir_name())
37}
38
39/// Get the read-only plugin seed directories.
40pub fn get_plugin_seed_dirs() -> Vec<PathBuf> {
41    let raw = match std::env::var("CLAUDE_CODE_PLUGIN_SEED_DIR") {
42        Ok(v) => v,
43        Err(_) => return Vec::new(),
44    };
45
46    let delimiter = if cfg!(windows) { ";" } else { ":" };
47
48    raw.split(delimiter)
49        .filter(|s| !s.is_empty())
50        .map(|s| PathBuf::from(expand_tilde(s)))
51        .collect()
52}
53
54/// Sanitize a plugin ID for use in file paths.
55fn sanitize_plugin_id(plugin_id: &str) -> String {
56    plugin_id
57        .chars()
58        .map(|c| {
59            if c.is_alphanumeric() || c == '-' || c == '_' {
60                c
61            } else {
62                '-'
63            }
64        })
65        .collect()
66}
67
68/// Get the path for a plugin's data directory (without creating it).
69pub fn plugin_data_dir_path(plugin_id: &str) -> PathBuf {
70    PathBuf::from(get_plugins_directory())
71        .join("data")
72        .join(sanitize_plugin_id(plugin_id))
73}
74
75/// Get or create the persistent per-plugin data directory.
76pub fn get_plugin_data_dir(plugin_id: &str) -> String {
77    let dir = plugin_data_dir_path(plugin_id);
78    std::fs::create_dir_all(&dir).ok();
79    dir.to_string_lossy().to_string()
80}
81
82/// Delete a plugin's data directory (best-effort).
83pub async fn delete_plugin_data_dir(plugin_id: &str) {
84    let dir = plugin_data_dir_path(plugin_id);
85    if let Err(e) = tokio::fs::remove_dir_all(&dir).await {
86        log::debug!("Failed to delete plugin data dir {:?}: {}", dir, e);
87    }
88}
89
90/// Expand ~ in a path to the home directory.
91fn expand_tilde(path: &str) -> String {
92    if path.starts_with("~/") || path == "~" {
93        if let Some(home) = dirs::home_dir() {
94            return format!("{}{}", home.display(), &path[1..]);
95        }
96    }
97    path.to_string()
98}