Skip to main content

agent_vault/core/
paths.rs

1use std::path::{Path, PathBuf};
2
3/// All path resolution for vault directories and files.
4pub struct VaultPaths {
5    /// Root of the repository (where .agent-vault/ lives).
6    root: PathBuf,
7}
8
9impl VaultPaths {
10    pub fn new(root: impl Into<PathBuf>) -> Self {
11        Self { root: root.into() }
12    }
13
14    /// `.agent-vault/`
15    pub fn vault_dir(&self) -> PathBuf {
16        self.root.join(".agent-vault")
17    }
18
19    /// `.agent-vault/config.yaml`
20    pub fn config_file(&self) -> PathBuf {
21        self.vault_dir().join("config.yaml")
22    }
23
24    /// `.agent-vault/owner.pub`
25    pub fn owner_pub_file(&self) -> PathBuf {
26        self.vault_dir().join("owner.pub")
27    }
28
29    /// `.agent-vault/manifest.yaml`
30    pub fn manifest_file(&self) -> PathBuf {
31        self.vault_dir().join("manifest.yaml")
32    }
33
34    /// `.agent-vault/.gitignore`
35    pub fn gitignore_file(&self) -> PathBuf {
36        self.vault_dir().join(".gitignore")
37    }
38
39    /// `.agent-vault/agents/`
40    pub fn agents_dir(&self) -> PathBuf {
41        self.vault_dir().join("agents")
42    }
43
44    /// `.agent-vault/agents/<name>/`
45    pub fn agent_dir(&self, name: &str) -> PathBuf {
46        self.agents_dir().join(name)
47    }
48
49    /// `.agent-vault/agents/<name>/public.key`
50    pub fn agent_pub_file(&self, name: &str) -> PathBuf {
51        self.agent_dir(name).join("public.key")
52    }
53
54    /// `.agent-vault/agents/<name>/private.key.escrow`
55    pub fn agent_escrow_file(&self, name: &str) -> PathBuf {
56        self.agent_dir(name).join("private.key.escrow")
57    }
58
59    /// `.agent-vault/secrets/`
60    pub fn secrets_dir(&self) -> PathBuf {
61        self.vault_dir().join("secrets")
62    }
63
64    /// `.agent-vault/secrets/<group>/<name>.enc`
65    pub fn secret_enc_file(&self, path: &str) -> PathBuf {
66        let (group, name) = split_secret_path(path);
67        self.secrets_dir().join(group).join(format!("{name}.enc"))
68    }
69
70    /// `.agent-vault/secrets/<group>/<name>.meta`
71    pub fn secret_meta_file(&self, path: &str) -> PathBuf {
72        let (group, name) = split_secret_path(path);
73        self.secrets_dir().join(group).join(format!("{name}.meta"))
74    }
75
76    pub fn root(&self) -> &Path {
77        &self.root
78    }
79}
80
81/// `~/.agent-vault/owner.key`
82pub fn owner_key_path() -> PathBuf {
83    home_vault_dir().join("owner.key")
84}
85
86/// `~/.agent-vault/agents/<name>.key`
87pub fn agent_key_path(name: &str) -> PathBuf {
88    home_vault_dir().join("agents").join(format!("{name}.key"))
89}
90
91/// `~/.agent-vault/`
92pub fn home_vault_dir() -> PathBuf {
93    dirs::home_dir()
94        .expect("could not determine home directory")
95        .join(".agent-vault")
96}
97
98/// Split `group/secret-name` into `("group", "secret-name")`.
99/// If no slash, treat the whole thing as the name under a "default" group.
100fn split_secret_path(path: &str) -> (&str, &str) {
101    match path.split_once('/') {
102        Some((group, name)) => (group, name),
103        None => ("default", path),
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_split_secret_path() {
113        assert_eq!(split_secret_path("stripe/api-key"), ("stripe", "api-key"));
114        assert_eq!(split_secret_path("my-secret"), ("default", "my-secret"));
115    }
116
117    #[test]
118    fn test_vault_paths() {
119        let paths = VaultPaths::new("/tmp/repo");
120        assert_eq!(paths.vault_dir(), PathBuf::from("/tmp/repo/.agent-vault"));
121        assert_eq!(
122            paths.secret_enc_file("stripe/api-key"),
123            PathBuf::from("/tmp/repo/.agent-vault/secrets/stripe/api-key.enc")
124        );
125    }
126}