use super::*;
pub async fn cmd_skills_list(url: &str, json: bool) -> Result<(), Box<dyn std::error::Error>> {
let (DIM, BOLD, ACCENT, GREEN, YELLOW, RED, CYAN, RESET, MONO) = colors();
let (OK, ACTION, WARN, DETAIL, ERR) = icons();
let c = RoboticusClient::new(url)?;
let data = c.get("/api/skills").await.map_err(|e| {
RoboticusClient::check_connectivity_hint(&*e);
e
})?;
if json {
println!("{}", serde_json::to_string_pretty(&data)?);
return Ok(());
}
heading("Skills");
let skills = data["skills"].as_array();
match skills {
Some(arr) if !arr.is_empty() => {
let widths = [22, 14, 34, 9];
table_header(&["Name", "Kind", "Description", "Enabled"], &widths);
for s in arr {
let name = s["name"].as_str().unwrap_or("").to_string();
let kind = s["kind"].as_str().unwrap_or("").to_string();
let desc = truncate_id(s["description"].as_str().unwrap_or(""), 31);
let enabled = if s["enabled"].as_bool().unwrap_or(false)
|| s["enabled"].as_i64().map(|v| v != 0).unwrap_or(false)
{
format!("{OK} yes")
} else {
format!("{RED}{ERR} no{RESET}")
};
table_row(
&[format!("{ACCENT}{name}{RESET}"), kind, desc, enabled],
&widths,
);
}
eprintln!();
eprintln!(" {DIM}{} skill(s){RESET}", arr.len());
}
_ => empty_state("No skills registered"),
}
eprintln!();
Ok(())
}
pub async fn cmd_skill_detail(
url: &str,
id: &str,
json: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let c = RoboticusClient::new(url)?;
let s = c.get(&format!("/api/skills/{id}")).await.map_err(|e| {
RoboticusClient::check_connectivity_hint(&*e);
e
})?;
if json {
println!("{}", serde_json::to_string_pretty(&s)?);
return Ok(());
}
heading(&format!("Skill: {}", s["name"].as_str().unwrap_or(id)));
kv_mono("ID", s["id"].as_str().unwrap_or(""));
kv_accent("Name", s["name"].as_str().unwrap_or(""));
kv("Kind", s["kind"].as_str().unwrap_or(""));
kv("Description", s["description"].as_str().unwrap_or(""));
kv_mono("Source", s["source_path"].as_str().unwrap_or(""));
kv_mono("Hash", s["content_hash"].as_str().unwrap_or(""));
let enabled = if s["enabled"].as_bool().unwrap_or(false)
|| s["enabled"].as_i64().map(|v| v != 0).unwrap_or(false)
{
status_badge("running")
} else {
status_badge("dead")
};
kv("Enabled", &enabled);
if let Some(triggers) = s["triggers_json"].as_str()
&& !triggers.is_empty()
&& triggers != "null"
{
kv("Triggers", triggers);
}
if let Some(script) = s["script_path"].as_str()
&& !script.is_empty()
&& script != "null"
{
kv_mono("Script", script);
}
eprintln!();
Ok(())
}
pub async fn cmd_skills_reload(url: &str) -> Result<(), Box<dyn std::error::Error>> {
let (DIM, BOLD, ACCENT, GREEN, YELLOW, RED, CYAN, RESET, MONO) = colors();
let (OK, ACTION, WARN, DETAIL, ERR) = icons();
let c = RoboticusClient::new(url)?;
c.post("/api/skills/reload", serde_json::json!({}))
.await
.map_err(|e| {
RoboticusClient::check_connectivity_hint(&*e);
e
})?;
eprintln!();
eprintln!(" {OK} Skills reloaded from disk");
eprintln!();
Ok(())
}
pub async fn cmd_skills_catalog_list(
url: &str,
query: Option<&str>,
json: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let c = RoboticusClient::new(url)?;
let path = if let Some(q) = query {
format!("/api/skills/catalog?q={}", crate::cli::urlencoding(q))
} else {
"/api/skills/catalog".to_string()
};
let data = c.get(&path).await?;
if json {
println!("{}", serde_json::to_string_pretty(&data)?);
return Ok(());
}
heading("Skills & Plugins");
let items = data["items"].as_array().cloned().unwrap_or_default();
let plugins = data["plugins"].as_array().cloned().unwrap_or_default();
if items.is_empty() && plugins.is_empty() {
empty_state("No catalog entries found");
eprintln!();
return Ok(());
}
if !items.is_empty() {
let (_, _, _, DETAIL, _) = icons();
eprintln!(" {DETAIL} Skills");
let widths = [28, 12, 50];
table_header(&["Name", "Kind", "Description"], &widths);
for item in items {
let name = item
.get("name")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let kind = item
.get("kind")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let description = item
.get("description")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
table_row(&[name, kind, description], &widths);
}
eprintln!();
}
if !plugins.is_empty() {
let (_, _, _, DETAIL, _) = icons();
eprintln!(" {DETAIL} Plugins");
let widths = [28, 10, 10, 42];
table_header(&["Name", "Version", "Tier", "Description"], &widths);
for plugin in plugins {
let name = plugin
.get("name")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let version = plugin
.get("version")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let tier = plugin
.get("tier")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let description = plugin
.get("description")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
table_row(&[name, version, tier, description], &widths);
}
eprintln!();
}
Ok(())
}
pub async fn cmd_skills_catalog_install(
url: &str,
skills: &[String],
activate: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let c = RoboticusClient::new(url)?;
heading("Skills & Plugins — Install");
let data = match c
.post(
"/api/skills/catalog/install",
serde_json::json!({ "skills": skills, "activate": activate }),
)
.await
{
Ok(d) => d,
Err(e) => {
let (_, _, WARN, _, _) = icons();
let msg = e.to_string();
if let Some(json_start) = msg.find('{')
&& let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&msg[json_start..])
&& let Some(detail) = parsed.get("error").and_then(|v| v.as_str())
{
eprintln!(" {WARN} {detail}");
eprintln!();
return Ok(());
}
eprintln!(" {WARN} {msg}");
eprintln!();
return Ok(());
}
};
if let Some(err) = data.get("error").and_then(|v| v.as_str()) {
let (_, _, WARN, _, _) = icons();
eprintln!(" {WARN} {err}");
} else {
kv("Installed", &data["installed"].to_string());
kv("Activated", &data["activated"].to_string());
}
eprintln!();
Ok(())
}
pub async fn cmd_skills_catalog_activate(
url: &str,
skills: &[String],
) -> Result<(), Box<dyn std::error::Error>> {
let c = RoboticusClient::new(url)?;
let _ = c
.post(
"/api/skills/catalog/activate",
serde_json::json!({ "skills": skills }),
)
.await?;
heading("Skills & Plugins — Activate");
kv("Requested", &skills.join(", "));
eprintln!();
Ok(())
}