use serde::Serialize;
use crate::output::CliReport;
use crate::output::human::Human;
#[derive(Serialize)]
pub struct SkillAddReport {
pub action: &'static str,
pub name: String,
pub dest: String,
}
impl CliReport for SkillAddReport {
fn render(&self, h: &mut Human<'_>) -> std::io::Result<()> {
h.line(&format!("{} {} → {}", self.action, self.name, self.dest))
}
}
#[derive(Serialize)]
pub struct SkillEntry {
pub name: String,
pub path: String,
}
#[derive(Serialize)]
pub struct SkillsListReport {
pub skills: Vec<SkillEntry>,
}
impl CliReport for SkillsListReport {
fn render(&self, h: &mut Human<'_>) -> std::io::Result<()> {
if self.skills.is_empty() {
return h.line("no skills installed");
}
for s in &self.skills {
h.line(&format!("{}\t{}", s.name, s.path))?;
}
Ok(())
}
}
#[derive(Serialize)]
pub struct SkillRemoveReport {
pub name: String,
pub path: String,
pub removed: bool,
}
impl CliReport for SkillRemoveReport {
fn render(&self, h: &mut Human<'_>) -> std::io::Result<()> {
h.line(&format!("removed {} ({})", self.name, self.path))
}
}
#[derive(Serialize)]
pub struct SkillHit {
pub name: String,
pub version: String,
pub description: String,
}
#[derive(Serialize)]
pub struct SkillsFindReport {
pub query: String,
pub registry: String,
pub results: Vec<SkillHit>,
}
impl CliReport for SkillsFindReport {
fn render(&self, h: &mut Human<'_>) -> std::io::Result<()> {
if self.results.is_empty() {
return h.line(&format!(
"no skills matching {:?} in {}",
self.query, self.registry
));
}
for r in &self.results {
h.line(&format!("{}\t{}\t{}", r.name, r.version, r.description))?;
}
Ok(())
}
}
#[derive(Serialize)]
pub struct SkillInitReport {
pub path: String,
pub created: bool,
}
impl CliReport for SkillInitReport {
fn render(&self, h: &mut Human<'_>) -> std::io::Result<()> {
h.line(&format!("wrote {}", self.path))
}
}
#[derive(Serialize)]
pub struct UpdatedSkill {
pub name: String,
pub dest: String,
}
#[derive(Serialize)]
pub struct SkippedSkill {
pub name: String,
pub reason: String,
}
#[derive(Serialize)]
pub struct FailedSkill {
pub name: String,
pub error: String,
}
#[derive(Serialize)]
pub struct SkillsUpdateReport {
pub updated: Vec<UpdatedSkill>,
pub skipped: Vec<SkippedSkill>,
pub failed: Vec<FailedSkill>,
}
impl CliReport for SkillsUpdateReport {
fn render(&self, h: &mut Human<'_>) -> std::io::Result<()> {
if self.updated.is_empty() && self.skipped.is_empty() && self.failed.is_empty() {
return h.line("no skills installed to update");
}
for u in &self.updated {
h.line(&format!("updated {} → {}", u.name, u.dest))?;
}
for s in &self.skipped {
h.line(&format!("skipped {} ({})", s.name, s.reason))?;
}
for f in &self.failed {
h.line(&format!("failed {}: {}", f.name, f.error))?;
}
Ok(())
}
fn exit_code(&self) -> i32 {
if self.failed.is_empty() { 0 } else { 1 }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::output::CliReport;
#[test]
fn skills_update_exit_code() {
let clean = SkillsUpdateReport {
updated: vec![],
skipped: vec![],
failed: vec![],
};
assert_eq!(clean.exit_code(), 0);
let failed = SkillsUpdateReport {
updated: vec![],
skipped: vec![],
failed: vec![FailedSkill {
name: "x".into(),
error: "e".into(),
}],
};
assert_eq!(failed.exit_code(), 1);
}
}