systemprompt-cli 0.1.22

systemprompt.io OS - CLI for agent orchestration, AI operations, and system management
Documentation
//! List available profiles with session status.

use systemprompt_cloud::{ProfilePath, ProjectContext, SessionKey, SessionStore};
use systemprompt_models::Profile;

use super::types::{ProfileInfo, ProfileListOutput};
use crate::CliConfig;
use crate::paths::ResolvedPaths;
use crate::shared::CommandResult;

pub fn execute(_config: &CliConfig) -> CommandResult<ProfileListOutput> {
    let project_ctx = ProjectContext::discover();
    let profiles_dir = project_ctx.profiles_dir();

    if !profiles_dir.exists() {
        return CommandResult::table(ProfileListOutput {
            profiles: Vec::new(),
        })
        .with_title("Available Profiles");
    }

    let discovered = discover_profiles(&profiles_dir);

    if discovered.is_empty() {
        return CommandResult::table(ProfileListOutput {
            profiles: Vec::new(),
        })
        .with_title("Available Profiles");
    }

    let store = {
        let dir = ResolvedPaths::discover().sessions_dir();
        SessionStore::load_or_create(&dir).ok()
    };

    let profiles = discovered
        .into_iter()
        .map(|info| build_profile_info(&info, store.as_ref()))
        .collect();

    let output = ProfileListOutput { profiles };

    CommandResult::table(output)
        .with_title("Available Profiles")
        .with_columns(vec![
            "name".to_string(),
            "routing".to_string(),
            "is_active".to_string(),
            "session_status".to_string(),
        ])
}

struct DiscoveredProfile {
    name: String,
    tenant_id: Option<String>,
}

fn discover_profiles(dir: &std::path::Path) -> Vec<DiscoveredProfile> {
    let entries = match std::fs::read_dir(dir) {
        Ok(e) => e,
        Err(e) => {
            tracing::debug!(path = %dir.display(), error = %e, "Failed to read profiles directory");
            return Vec::new();
        },
    };

    entries
        .filter_map(Result::ok)
        .filter(|e| e.path().is_dir())
        .filter_map(|e| {
            let entry_path = e.path();
            let config_path = ProfilePath::Config.resolve(&entry_path);

            if !config_path.exists() {
                return None;
            }

            let name = e.file_name().to_str()?.to_string();
            let profile = load_profile_config(&config_path);

            Some(DiscoveredProfile {
                name,
                tenant_id: profile
                    .as_ref()
                    .and_then(|p| p.cloud.as_ref())
                    .and_then(|c| c.tenant_id.clone()),
            })
        })
        .collect()
}

fn load_profile_config(config_path: &std::path::Path) -> Option<Profile> {
    let content = std::fs::read_to_string(config_path).ok()?;
    Profile::parse(&content, config_path).ok()
}

fn build_profile_info(info: &DiscoveredProfile, store: Option<&SessionStore>) -> ProfileInfo {
    let session_key = SessionKey::from_tenant_id(info.tenant_id.as_deref());

    let is_active = store.is_some_and(|s| {
        s.active_profile_name.as_deref() == Some(info.name.as_str())
            || (s.active_profile_name.is_none()
                && s.active_key.as_ref() == Some(&session_key.as_storage_key()))
    });

    let session_status = store.map_or_else(
        || "unknown".to_string(),
        |s| match s.get_session(&session_key) {
            Some(session) if session.is_expired() => "expired".to_string(),
            Some(session) => {
                let remaining = session.expires_at - chrono::Utc::now();
                let hours = remaining.num_hours();
                let minutes = remaining.num_minutes() % 60;
                format!("{}h {}m remaining", hours, minutes)
            },
            None => "no session".to_string(),
        },
    );

    let routing = info
        .tenant_id
        .as_ref()
        .map_or_else(|| "local".to_string(), |_| "remote".to_string());

    ProfileInfo {
        name: info.name.clone(),
        routing,
        is_active,
        session_status,
    }
}