agentmux_srv\registry/
paths.rs

1// Copyright 2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Resolve the shared registry directory (`<shared_home>/agents/registry/`).
5
6use std::path::PathBuf;
7
8/// Resolve `<shared_home>/agents/registry/`.
9///
10/// The registry is shared across every portable/installed version on
11/// the same machine — it must NOT use the per-version data dir.
12///
13/// Resolution order:
14/// 1. `AGENTMUX_HOME_OVERRIDE` — test-only escape hatch, matching
15///    `agentmux-common::data_paths` convention.
16/// 2. Walk up from `AGENTMUX_DATA_DIR` (per-version data dir set by
17///    the launcher) by three levels: `data → versions/<v> → versions
18///    → <shared_home>`. Robust against the launcher running without
19///    a real home dir (CI).
20/// 3. Fall back to `~/.agentmux/`.
21///
22/// Returns `None` only if every source fails — caller treats this as
23/// "registry disabled," no write attempts.
24pub fn resolve_shared_registry_dir() -> Option<PathBuf> {
25    resolve_shared_home().map(|h| h.join("agents").join("registry"))
26}
27
28fn resolve_shared_home() -> Option<PathBuf> {
29    if let Ok(s) = std::env::var("AGENTMUX_HOME_OVERRIDE") {
30        if !s.is_empty() {
31            return Some(PathBuf::from(s));
32        }
33    }
34    if let Ok(s) = std::env::var("AGENTMUX_DATA_DIR") {
35        if !s.is_empty() {
36            let p = PathBuf::from(s);
37            // .../versions/<v>/data → ancestors()[3] = .../
38            if let Some(root) = p.ancestors().nth(3) {
39                if !root.as_os_str().is_empty() {
40                    return Some(root.to_path_buf());
41                }
42            }
43        }
44    }
45    dirs::home_dir().map(|h| h.join(".agentmux"))
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use std::sync::Mutex;
52
53    // Process-global env access — serialize so parallel tests don't race.
54    static ENV_LOCK: Mutex<()> = Mutex::new(());
55
56    fn clear() {
57        std::env::remove_var("AGENTMUX_HOME_OVERRIDE");
58        std::env::remove_var("AGENTMUX_DATA_DIR");
59    }
60
61    #[test]
62    fn override_wins() {
63        let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
64        clear();
65        std::env::set_var("AGENTMUX_HOME_OVERRIDE", "/tmp/test-home");
66        let r = resolve_shared_registry_dir().unwrap();
67        assert_eq!(r, PathBuf::from("/tmp/test-home/agents/registry"));
68        clear();
69    }
70
71    #[test]
72    fn walks_up_from_data_dir() {
73        let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
74        clear();
75        std::env::set_var(
76            "AGENTMUX_DATA_DIR",
77            "/home/user/.agentmux/versions/0.33.822/data",
78        );
79        let r = resolve_shared_registry_dir().unwrap();
80        assert_eq!(r, PathBuf::from("/home/user/.agentmux/agents/registry"));
81        clear();
82    }
83
84    #[test]
85    fn falls_back_to_home() {
86        let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
87        clear();
88        // No env set — uses dirs::home_dir(). Just assert non-None.
89        assert!(resolve_shared_registry_dir().is_some());
90    }
91}