systemprompt-cli 0.1.22

systemprompt.io OS - CLI for agent orchestration, AI operations, and system management
Documentation
use systemprompt_cloud::{LOCAL_SESSION_KEY, SessionKey, SessionStore, TenantStore};

use super::types::{RoutingInfo, SessionInfo, SessionShowOutput};
use crate::CliConfig;
use crate::paths::ResolvedPaths;
use crate::shared::CommandResult;

pub fn execute(_config: &CliConfig) -> CommandResult<SessionShowOutput> {
    let paths = ResolvedPaths::discover();

    let sessions = collect_sessions(&paths);
    let routing = collect_routing_info(&paths);

    let output = SessionShowOutput { sessions, routing };

    CommandResult::card(output).with_title("Session Info")
}

fn collect_sessions(paths: &ResolvedPaths) -> Vec<SessionInfo> {
    let sessions_dir = paths.sessions_dir();

    let Ok(store) = SessionStore::load_or_create(&sessions_dir) else {
        return Vec::new();
    };

    let active_key = store.active_key.clone();
    let active_profile = store.active_profile_name.clone();

    let mut results = Vec::new();
    let mut displayed_active = false;

    for (key, session) in store.all_sessions() {
        let is_active = active_key.as_ref() == Some(key);
        if is_active {
            displayed_active = true;
        }

        let display_key = if key == LOCAL_SESSION_KEY {
            "local".to_string()
        } else {
            key.strip_prefix("tenant_")
                .map_or_else(|| key.clone(), String::from)
        };

        let expires_in = if session.is_expired() {
            None
        } else {
            let remaining = session.expires_at - chrono::Utc::now();
            let hours = remaining.num_hours();
            let minutes = remaining.num_minutes() % 60;
            Some(format!("{}h {}m", hours, minutes))
        };

        let stale_warning = {
            let context_age = chrono::Utc::now() - session.last_used;
            if context_age.num_hours() > 24 {
                Some(format!(
                    "Context may be stale (last used {}h ago). Re-login with --force-new if \
                     commands fail.",
                    context_age.num_hours()
                ))
            } else {
                None
            }
        };

        results.push(SessionInfo {
            key: display_key,
            profile_name: session.profile_name.as_str().to_string(),
            user_email: session.user_email.as_str().to_string(),
            session_id: session.session_id.as_str().to_string(),
            context_id: session.context_id.as_str().to_string(),
            is_active,
            is_expired: session.is_expired(),
            expires_in,
            stale_warning,
        });
    }

    if !displayed_active && (active_key.is_some() || active_profile.is_some()) {
        let display_name = active_profile.as_deref().unwrap_or_else(|| {
            active_key.as_deref().map_or("unknown", |k| {
                if k == LOCAL_SESSION_KEY {
                    "local"
                } else {
                    k.strip_prefix("tenant_").unwrap_or(k)
                }
            })
        });

        results.push(SessionInfo {
            key: display_name.to_string(),
            profile_name: display_name.to_string(),
            user_email: String::new(),
            session_id: String::new(),
            context_id: String::new(),
            is_active: true,
            is_expired: false,
            expires_in: None,
            stale_warning: Some(
                "No session. Run 'systemprompt admin session login' to create a session."
                    .to_string(),
            ),
        });
    }

    results
}

fn collect_routing_info(paths: &ResolvedPaths) -> Option<RoutingInfo> {
    let sessions_dir = paths.sessions_dir();
    let store = SessionStore::load_or_create(&sessions_dir).ok()?;
    let active_key = store.active_session_key()?;

    let session = store.sessions.get(&active_key.as_storage_key());

    let profile_name = session
        .map(|s| s.profile_name.as_str().to_string())
        .or_else(|| store.active_profile_name.clone())
        .unwrap_or_else(|| "unknown".to_string());

    match &active_key {
        SessionKey::Local => Some(RoutingInfo {
            profile_name,
            target: "Local".to_string(),
            tenant_id: None,
            hostname: None,
        }),
        SessionKey::Tenant(tenant_id) => {
            let hostname = resolve_remote_hostname(paths, tenant_id.as_str());
            Some(RoutingInfo {
                profile_name,
                target: if hostname.is_some() {
                    "Remote".to_string()
                } else {
                    "Tenant".to_string()
                },
                tenant_id: Some(tenant_id.as_str().to_string()),
                hostname,
            })
        },
    }
}

fn resolve_remote_hostname(paths: &ResolvedPaths, tenant_id: &str) -> Option<String> {
    let tenants_path = paths.tenants_path();
    let store = TenantStore::load_from_path(&tenants_path).ok()?;
    let tenant = store.find_tenant(tenant_id)?;
    tenant.hostname.clone()
}