Skip to main content

systemprompt_cloud/paths/
context.rs

1//! [`UnifiedContext`]: resolves credential, tenant, and session file locations
2//! from a discovered project, falling back to configured cloud paths.
3//!
4//! Precedence is explicit cloud paths first, then the discovered project, then
5//! a `.systemprompt`-relative default, so the same accessor works whether
6//! running in a local checkout or a deployed container.
7
8use std::path::{Path, PathBuf};
9
10use super::{CloudPath, CloudPaths, DiscoveredProject};
11use crate::constants::{dir_names, file_names};
12
13#[derive(Debug, Clone)]
14pub struct UnifiedContext {
15    project: Option<DiscoveredProject>,
16    cloud_paths: Option<CloudPaths>,
17}
18
19impl UnifiedContext {
20    #[must_use]
21    pub fn discover() -> Self {
22        let project = DiscoveredProject::discover();
23        Self {
24            project,
25            cloud_paths: None,
26        }
27    }
28
29    #[must_use]
30    pub fn discover_from(start: &Path) -> Self {
31        let project = DiscoveredProject::discover_from(start);
32        Self {
33            project,
34            cloud_paths: None,
35        }
36    }
37
38    pub fn with_profile_paths(
39        mut self,
40        profile_dir: &Path,
41        credentials_path: &str,
42        tenants_path: &str,
43    ) -> Self {
44        self.cloud_paths = Some(CloudPaths::from_config(
45            profile_dir,
46            credentials_path,
47            tenants_path,
48        ));
49        self
50    }
51
52    #[must_use]
53    pub const fn has_project(&self) -> bool {
54        self.project.is_some()
55    }
56
57    #[must_use]
58    pub fn project_root(&self) -> Option<&Path> {
59        self.project.as_ref().map(DiscoveredProject::root)
60    }
61
62    #[must_use]
63    pub fn systemprompt_dir(&self) -> Option<PathBuf> {
64        self.project
65            .as_ref()
66            .map(DiscoveredProject::systemprompt_dir)
67            .map(Path::to_path_buf)
68    }
69
70    #[must_use]
71    pub fn credentials_path(&self) -> PathBuf {
72        if let Some(cloud) = &self.cloud_paths {
73            return cloud.resolve(CloudPath::Credentials);
74        }
75        if let Some(project) = &self.project {
76            return project.credentials_path();
77        }
78        PathBuf::from(dir_names::SYSTEMPROMPT).join(file_names::CREDENTIALS)
79    }
80
81    #[must_use]
82    pub fn tenants_path(&self) -> PathBuf {
83        if let Some(cloud) = &self.cloud_paths {
84            return cloud.resolve(CloudPath::Tenants);
85        }
86        if let Some(project) = &self.project {
87            return project.tenants_path();
88        }
89        PathBuf::from(dir_names::SYSTEMPROMPT).join(file_names::TENANTS)
90    }
91
92    #[must_use]
93    pub fn session_path(&self) -> PathBuf {
94        if let Some(cloud) = &self.cloud_paths {
95            return cloud.resolve(CloudPath::CliSession);
96        }
97        if let Some(project) = &self.project {
98            return project.session_path();
99        }
100        PathBuf::from(dir_names::SYSTEMPROMPT).join(file_names::SESSION)
101    }
102
103    #[must_use]
104    pub fn profiles_dir(&self) -> Option<PathBuf> {
105        self.project.as_ref().map(DiscoveredProject::profiles_dir)
106    }
107
108    #[must_use]
109    pub fn profile_dir(&self, name: &str) -> Option<PathBuf> {
110        self.project.as_ref().map(|p| p.profile_dir(name))
111    }
112
113    #[must_use]
114    pub fn docker_dir(&self) -> Option<PathBuf> {
115        self.project.as_ref().map(DiscoveredProject::docker_dir)
116    }
117
118    #[must_use]
119    pub fn storage_dir(&self) -> Option<PathBuf> {
120        self.project.as_ref().map(DiscoveredProject::storage_dir)
121    }
122
123    #[must_use]
124    pub fn has_credentials(&self) -> bool {
125        self.credentials_path().exists()
126    }
127
128    #[must_use]
129    pub fn has_tenants(&self) -> bool {
130        self.tenants_path().exists()
131    }
132
133    #[must_use]
134    pub fn has_session(&self) -> bool {
135        self.session_path().exists()
136    }
137
138    #[must_use]
139    pub fn has_profile(&self, name: &str) -> bool {
140        self.project.as_ref().is_some_and(|p| p.has_profile(name))
141    }
142
143    #[must_use]
144    pub const fn project(&self) -> Option<&DiscoveredProject> {
145        self.project.as_ref()
146    }
147
148    #[must_use]
149    pub const fn cloud_paths(&self) -> Option<&CloudPaths> {
150        self.cloud_paths.as_ref()
151    }
152}
153
154impl Default for UnifiedContext {
155    fn default() -> Self {
156        Self::discover()
157    }
158}