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