systemprompt-cli 0.2.1

Unified CLI for systemprompt.io AI governance: agent orchestration, MCP governance, analytics, profiles, cloud deploy, and self-hosted operations.
Documentation
const KNOWN_TABLES: &[&str] = &[
    "logs",
    "ai_requests",
    "mcp_tool_executions",
    "agent_tasks",
    "users",
    "tenants",
    "sessions",
    "agent_execution_steps",
    "agent_artifacts",
    "credentials",
    "mcp_servers",
    "workflow_states",
    "blog_posts",
    "categories",
    "sources",
];

pub fn format_bytes(bytes: i64) -> String {
    const KB: i64 = 1024;
    const MB: i64 = KB * 1024;
    const GB: i64 = MB * 1024;

    if bytes >= GB {
        format!("{:.2} GB", bytes as f64 / GB as f64)
    } else if bytes >= MB {
        format!("{:.2} MB", bytes as f64 / MB as f64)
    } else if bytes >= KB {
        format!("{:.2} KB", bytes as f64 / KB as f64)
    } else {
        format!("{} bytes", bytes)
    }
}

pub fn extract_relation_name(msg: &str) -> String {
    if let Some(start) = msg.find('"') {
        if let Some(end) = msg[start + 1..].find('"') {
            return msg[start + 1..start + 1 + end].to_string();
        }
    }
    "unknown".to_string()
}

pub fn suggest_table_name(input: &str) -> Option<String> {
    let input_lower = input.to_lowercase();
    let input_parts: Vec<&str> = input_lower.split('_').collect();

    KNOWN_TABLES
        .iter()
        .filter(|&&table| {
            let table_lower = table.to_lowercase();
            let table_parts: Vec<&str> = table_lower.split('_').collect();

            table_lower.contains(&input_lower)
                || input_lower.contains(&table_lower)
                || levenshtein_distance(&input_lower, &table_lower) <= 4
                || shares_prefix_parts(&input_parts, &table_parts, 2)
        })
        .min_by_key(|&&table| levenshtein_distance(&input_lower, &table.to_lowercase()))
        .map(|&s| s.to_string())
}

fn shares_prefix_parts(a: &[&str], b: &[&str], min_shared: usize) -> bool {
    let shared = a.iter().zip(b.iter()).take_while(|(x, y)| x == y).count();
    shared >= min_shared
}

fn levenshtein_distance(s1: &str, s2: &str) -> usize {
    let len1 = s1.chars().count();
    let len2 = s2.chars().count();

    if len1 == 0 {
        return len2;
    }
    if len2 == 0 {
        return len1;
    }

    let mut prev_row: Vec<usize> = (0..=len2).collect();
    let mut curr_row: Vec<usize> = vec![0; len2 + 1];

    for (i, c1) in s1.chars().enumerate() {
        curr_row[0] = i + 1;

        for (j, c2) in s2.chars().enumerate() {
            let cost = usize::from(c1 != c2);
            curr_row[j + 1] = (prev_row[j + 1] + 1)
                .min(curr_row[j] + 1)
                .min(prev_row[j] + cost);
        }

        std::mem::swap(&mut prev_row, &mut curr_row);
    }

    prev_row[len2]
}