Skip to main content

hermes_agent_cli_core/
profiles.rs

1// Profile management - helper functions for profile file operations
2
3use crate::config::Config;
4use anyhow::{Context, Result};
5use std::fs;
6use std::path::PathBuf;
7
8/// Returns the profiles directory path: HERMES_HOME/profiles/
9pub fn profiles_dir() -> PathBuf {
10    Config::hermes_home().join("profiles")
11}
12
13/// Returns the path to a specific profile: HERMES_HOME/profiles/<name>.yaml
14pub fn profile_path(name: &str) -> PathBuf {
15    profiles_dir().join(format!("{}.yaml", name))
16}
17
18/// Creates the profiles directory if it doesn't exist
19pub fn ensure_profiles_dir() -> Result<()> {
20    let dir = profiles_dir();
21    if !dir.exists() {
22        fs::create_dir_all(&dir)
23            .with_context(|| format!("failed to create profiles directory at {:?}", dir))?;
24    }
25    Ok(())
26}
27
28/// Lists all profile names (without .yaml extension)
29pub fn list_profiles() -> Result<Vec<String>> {
30    ensure_profiles_dir()?;
31    let dir = profiles_dir();
32    let mut profiles = Vec::new();
33
34    let entries = fs::read_dir(&dir)
35        .with_context(|| format!("failed to read profiles directory {:?}", dir))?;
36
37    for entry in entries {
38        let entry = entry?;
39        let path = entry.path();
40        if path.extension().map(|e| e == "yaml").unwrap_or(false) {
41            if let Some(stem) = path.file_stem() {
42                profiles.push(stem.to_string_lossy().to_string());
43            }
44        }
45    }
46
47    profiles.sort();
48    Ok(profiles)
49}
50
51/// Loads a profile by name, returning the Config
52pub fn load_profile(name: &str) -> Result<Config> {
53    let path = profile_path(name);
54    if !path.exists() {
55        anyhow::bail!("profile '{}' not found at {:?}", name, path);
56    }
57    let content = fs::read_to_string(&path)
58        .with_context(|| format!("failed to read profile from {:?}", path))?;
59    let config: Config = serde_yaml::from_str(&content)
60        .with_context(|| format!("failed to parse profile from {:?}", path))?;
61    Ok(config)
62}
63
64/// Saves a config as a profile with the given name
65pub fn save_profile(name: &str, config: &Config) -> Result<()> {
66    ensure_profiles_dir()?;
67    let path = profile_path(name);
68    let content = serde_yaml::to_string(config).context("failed to serialize profile")?;
69    fs::write(&path, content).with_context(|| format!("failed to write profile to {:?}", path))?;
70    Ok(())
71}
72
73/// Copies a profile from one name to another
74pub fn clone_profile(from_name: &str, to_name: &str) -> Result<()> {
75    let config = load_profile(from_name)?;
76    save_profile(to_name, &config)?;
77    Ok(())
78}
79
80/// Deletes a profile by name
81pub fn delete_profile(name: &str) -> Result<()> {
82    let path = profile_path(name);
83    if !path.exists() {
84        anyhow::bail!("profile '{}' not found", name);
85    }
86    fs::remove_file(&path).with_context(|| format!("failed to delete profile at {:?}", path))?;
87    Ok(())
88}
89
90/// Renames a profile (file rename)
91pub fn rename_profile(old_name: &str, new_name: &str) -> Result<()> {
92    let old_path = profile_path(old_name);
93    let new_path = profile_path(new_name);
94
95    if !old_path.exists() {
96        anyhow::bail!("profile '{}' not found", old_name);
97    }
98    if new_path.exists() {
99        anyhow::bail!("profile '{}' already exists", new_name);
100    }
101
102    fs::rename(&old_path, &new_path).with_context(|| {
103        format!("failed to rename profile from {:?} to {:?}", old_path, new_path)
104    })?;
105    Ok(())
106}
107
108/// Checks if a profile exists
109pub fn profile_exists(name: &str) -> bool {
110    profile_path(name).exists()
111}
112
113/// Gets the active profile name from HERMES_PROFILE env var, or "default" if not set
114pub fn get_active_profile() -> String {
115    std::env::var("HERMES_PROFILE").unwrap_or_else(|_| "default".to_string())
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn test_profiles_dir() {
124        let dir = profiles_dir();
125        assert!(dir.ends_with("profiles"));
126    }
127
128    #[test]
129    fn test_profile_path() {
130        let path = profile_path("myprofile");
131        assert!(path.ends_with("myprofile.yaml"));
132    }
133}