systemprompt-cli 0.1.22

systemprompt.io OS - CLI for agent orchestration, AI operations, and system management
Documentation
use anyhow::{Context, Result};
use clap::Args;
use std::sync::Arc;

use super::types::{SkillStatusOutput, SkillStatusRow, SkillStatusSummary, SyncStatus};
use crate::CliConfig;
use crate::shared::CommandResult;
use systemprompt_database::{Database, DbPool};
use systemprompt_logging::CliService;
use systemprompt_models::{ProfileBootstrap, SecretsBootstrap};
use systemprompt_sync::diff::SkillsDiffCalculator;

#[derive(Debug, Args)]
pub struct StatusArgs {
    #[arg(help = "Skill ID to show status (optional)")]
    pub name: Option<String>,
}

pub async fn execute(
    args: StatusArgs,
    _config: &CliConfig,
) -> Result<CommandResult<SkillStatusOutput>> {
    CliService::section("Skills Database Status");

    let db = create_db_provider().await?;
    let skills_path = get_skills_path()?;

    let calculator = SkillsDiffCalculator::new(&db)?;
    let diff = calculator
        .calculate_diff(&skills_path)
        .await
        .context("Failed to calculate diff")?;

    let mut skills: Vec<SkillStatusRow> = Vec::new();

    for item in &diff.added {
        if let Some(ref name) = args.name {
            if item.skill_id.as_ref() != name.as_str() {
                continue;
            }
        }
        skills.push(SkillStatusRow {
            skill_id: item.skill_id.to_string(),
            name: item.name.clone().unwrap_or_else(String::new),
            on_disk: true,
            in_db: false,
            status: SyncStatus::DiskOnly,
        });
    }

    for item in &diff.removed {
        if let Some(ref name) = args.name {
            if item.skill_id.as_ref() != name.as_str() {
                continue;
            }
        }
        skills.push(SkillStatusRow {
            skill_id: item.skill_id.to_string(),
            name: item.name.clone().unwrap_or_else(String::new),
            on_disk: false,
            in_db: true,
            status: SyncStatus::DbOnly,
        });
    }

    for item in &diff.modified {
        if let Some(ref name) = args.name {
            if item.skill_id.as_ref() != name.as_str() {
                continue;
            }
        }
        skills.push(SkillStatusRow {
            skill_id: item.skill_id.to_string(),
            name: item.name.clone().unwrap_or_else(String::new),
            on_disk: true,
            in_db: true,
            status: SyncStatus::Modified,
        });
    }

    let synced_count = if args.name.is_some() {
        0
    } else {
        diff.unchanged
    };

    skills.sort_by(|a, b| a.skill_id.cmp(&b.skill_id));

    let summary = SkillStatusSummary {
        total: skills.len() + synced_count,
        synced: synced_count,
        disk_only: diff.added.len(),
        db_only: diff.removed.len(),
        modified: diff.modified.len(),
    };

    display_summary(&summary);

    let output = SkillStatusOutput { skills, summary };

    Ok(CommandResult::table(output)
        .with_title("Skills Sync Status")
        .with_columns(vec![
            "skill_id".to_string(),
            "name".to_string(),
            "on_disk".to_string(),
            "in_db".to_string(),
            "status".to_string(),
        ]))
}

fn get_skills_path() -> Result<std::path::PathBuf> {
    let profile = ProfileBootstrap::get().context("Failed to get profile")?;
    Ok(std::path::PathBuf::from(profile.paths.skills()))
}

async fn create_db_provider() -> Result<DbPool> {
    let url = SecretsBootstrap::database_url()
        .context("Database URL not configured")?
        .to_string();

    let database = Database::from_config("postgres", &url)
        .await
        .context("Failed to connect to database")?;

    Ok(Arc::new(database))
}

fn display_summary(summary: &SkillStatusSummary) {
    CliService::key_value("Total", &summary.total.to_string());
    CliService::key_value("Synced", &summary.synced.to_string());

    if summary.disk_only > 0 {
        CliService::key_value("Disk Only", &format!("+ {}", summary.disk_only));
    }

    if summary.db_only > 0 {
        CliService::key_value("DB Only", &format!("- {}", summary.db_only));
    }

    if summary.modified > 0 {
        CliService::key_value("Modified", &format!("~ {}", summary.modified));
    }
}