systemprompt_cli/commands/admin/session/
show.rs1use systemprompt_cloud::{CliSession, LOCAL_SESSION_KEY, SessionKey, SessionStore, TenantStore};
7
8use super::types::{RoutingInfo, SessionInfo, SessionShowOutput};
9use crate::CliConfig;
10use crate::paths::ResolvedPaths;
11use crate::shared::CommandOutput;
12
13pub(super) fn execute(_config: &CliConfig) -> CommandOutput {
14 let paths = ResolvedPaths::discover();
15
16 let sessions = collect_sessions(&paths);
17 let routing = collect_routing_info(&paths);
18
19 let output = SessionShowOutput { sessions, routing };
20
21 CommandOutput::card_value("Session Info", &output)
22}
23
24fn collect_sessions(paths: &ResolvedPaths) -> Vec<SessionInfo> {
25 let sessions_dir = paths.sessions_dir();
26
27 let store = SessionStore::load_or_reset(&sessions_dir);
28
29 let active_key = store.active_key.clone();
30 let active_profile = store.active_profile_name.clone();
31
32 let mut results = Vec::new();
33 let mut displayed_active = false;
34
35 for (key, session) in store.all_sessions() {
36 let is_active = active_key.as_ref() == Some(key);
37 if is_active {
38 displayed_active = true;
39 }
40 results.push(session_info(key, session, is_active));
41 }
42
43 if !displayed_active && (active_key.is_some() || active_profile.is_some()) {
44 results.push(missing_active_session(
45 active_key.as_deref(),
46 active_profile.as_deref(),
47 ));
48 }
49
50 results
51}
52
53pub fn session_info(key: &str, session: &CliSession, is_active: bool) -> SessionInfo {
54 let display_key = if key == LOCAL_SESSION_KEY {
55 "local".to_owned()
56 } else {
57 key.strip_prefix("tenant_")
58 .map_or_else(|| key.to_owned(), String::from)
59 };
60
61 let expires_in = if session.is_expired() {
62 None
63 } else {
64 let remaining = session.expires_at - chrono::Utc::now();
65 let hours = remaining.num_hours();
66 let minutes = remaining.num_minutes() % 60;
67 Some(format!("{}h {}m", hours, minutes))
68 };
69
70 let stale_warning = {
71 let context_age = chrono::Utc::now() - session.last_used;
72 if context_age.num_hours() > 24 {
73 Some(format!(
74 "Context may be stale (last used {}h ago). Re-login with --force-new if commands \
75 fail.",
76 context_age.num_hours()
77 ))
78 } else {
79 None
80 }
81 };
82
83 SessionInfo {
84 key: display_key,
85 profile_name: session.profile_name.as_str().to_owned(),
86 user_email: session.user_email.as_str().to_owned(),
87 session_id: Some(session.session_id.clone()),
88 context_id: Some(session.context_id.clone()),
89 is_active,
90 is_expired: session.is_expired(),
91 expires_in,
92 stale_warning,
93 }
94}
95
96pub fn missing_active_session(
97 active_key: Option<&str>,
98 active_profile: Option<&str>,
99) -> SessionInfo {
100 let display_name = active_profile.unwrap_or_else(|| {
101 active_key.map_or("unknown", |k| {
102 if k == LOCAL_SESSION_KEY {
103 "local"
104 } else {
105 k.strip_prefix("tenant_").unwrap_or(k)
106 }
107 })
108 });
109
110 SessionInfo {
111 key: display_name.to_owned(),
112 profile_name: display_name.to_owned(),
113 user_email: String::new(),
114 session_id: None,
115 context_id: None,
116 is_active: true,
117 is_expired: false,
118 expires_in: None,
119 stale_warning: Some(
120 "No session. Run 'systemprompt admin session login' to create a session.".to_owned(),
121 ),
122 }
123}
124
125fn collect_routing_info(paths: &ResolvedPaths) -> Option<RoutingInfo> {
126 let sessions_dir = paths.sessions_dir();
127 let store = SessionStore::load_or_reset(&sessions_dir);
128 let active_key = store.active_session_key()?;
129
130 let session = store.sessions.get(&active_key.as_storage_key());
131
132 let profile_name = session
133 .map(|s| s.profile_name.as_str().to_owned())
134 .or_else(|| store.active_profile_name.clone())
135 .unwrap_or_else(|| "unknown".to_owned());
136
137 match &active_key {
138 SessionKey::Local => Some(RoutingInfo {
139 profile_name,
140 target: "Local".to_owned(),
141 tenant: None,
142 hostname: None,
143 }),
144 SessionKey::Tenant(tenant_id) => {
145 let hostname = resolve_remote_hostname(paths, tenant_id.as_str());
146 Some(RoutingInfo {
147 profile_name,
148 target: if hostname.is_some() {
149 "Remote".to_owned()
150 } else {
151 "Tenant".to_owned()
152 },
153 tenant: Some(tenant_id.as_str().to_owned()),
154 hostname,
155 })
156 },
157 }
158}
159
160fn resolve_remote_hostname(paths: &ResolvedPaths, tenant: &str) -> Option<String> {
161 let tenants_path = paths.tenants_path();
162 let store = TenantStore::load_from_path(&tenants_path).ok()?;
163 let tenant = store.find_tenant(&systemprompt_identifiers::TenantId::new(tenant))?;
164 tenant.hostname.clone()
165}