use std::path::PathBuf;
use bamboo_skills::{SkillStore, SkillStoreConfig};
use colored::Colorize;
fn resolve_data_dir(data_dir: Option<PathBuf>) -> PathBuf {
data_dir.unwrap_or_else(bamboo_config::paths::resolve_bamboo_dir)
}
fn truncate(s: &str, max: usize) -> String {
if s.chars().count() <= max {
return s.to_string();
}
let head: String = s.chars().take(max.saturating_sub(1)).collect();
format!("{head}…")
}
pub async fn skills_list(data_dir: Option<PathBuf>) -> anyhow::Result<()> {
let data_dir = resolve_data_dir(data_dir);
let skills_dir = data_dir.join("skills");
let store = SkillStore::new(SkillStoreConfig {
skills_dir: skills_dir.clone(),
project_dir: None,
active_mode: None,
});
let skills = store.list_skills(None, true).await;
if skills.is_empty() {
println!(
"(no skills in {})",
bamboo_config::paths::path_to_display_string(&skills_dir)
);
return Ok(());
}
println!("{:<28} {:<32} DESCRIPTION", "ID", "NAME");
for skill in &skills {
println!(
"{:<28} {:<32} {}",
truncate(&skill.id, 28),
truncate(&skill.name, 32),
truncate(&skill.description, 60)
);
}
println!("\n{} skill(s).", skills.len());
Ok(())
}
pub async fn mcp_list(data_dir: Option<PathBuf>) -> anyhow::Result<()> {
use bamboo_domain::mcp_config::TransportConfig;
let data_dir = resolve_data_dir(data_dir);
let config = bamboo_config::Config::from_data_dir(Some(data_dir));
let servers = &config.mcp.servers;
if servers.is_empty() {
println!("(no MCP servers configured)");
return Ok(());
}
println!("{:<24} {:<8} {:<16} NAME", "ID", "ENABLED", "TRANSPORT");
for server in servers {
let transport = match &server.transport {
TransportConfig::Stdio(_) => "stdio",
TransportConfig::Sse(_) => "sse",
TransportConfig::StreamableHttp(_) => "streamable_http",
};
let name = server.name.as_deref().unwrap_or("");
println!(
"{:<24} {:<8} {:<16} {}",
truncate(&server.id, 24),
if server.enabled { "yes" } else { "no" },
transport,
name
);
}
let live = servers.iter().filter(|s| s.enabled).count();
println!(
"\n{live} enabled of {}. Live status: {}",
servers.len(),
"bamboo status".cyan()
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn truncate_shortens_long_cells() {
assert_eq!(truncate("hello", 10), "hello");
assert_eq!(truncate("hello-world", 5), "hell…");
}
#[tokio::test]
async fn skills_list_empty_dir_is_ok() {
let dir = tempfile::tempdir().expect("tempdir");
assert!(skills_list(Some(dir.path().to_path_buf())).await.is_ok());
}
}