spec_driven_docs/commands/
skill.rs1use camino::Utf8PathBuf;
8
9use crate::cli::skill::{Agent, SkillArgs, SkillCommand};
10use crate::context::AppContext;
11use crate::error::AppError;
12use crate::output;
13use crate::services::skill_installer;
14
15fn roots(agent: Agent) -> Result<Vec<Utf8PathBuf>, AppError> {
16 let home = std::env::var("HOME")
17 .ok()
18 .filter(|home| !home.is_empty())
19 .ok_or_else(|| AppError::Usage("HOME is not set".to_string()))?;
20 let home = Utf8PathBuf::from(home);
21 let mut roots = Vec::new();
22 if matches!(agent, Agent::Codex | Agent::All) {
23 roots.push(home.join(".agents/skills"));
24 }
25 if matches!(agent, Agent::Claude | Agent::All) {
26 roots.push(home.join(".claude/skills"));
27 }
28 Ok(roots)
29}
30
31pub fn run(_ctx: &AppContext, args: SkillArgs) -> Result<(), AppError> {
38 match args.command {
39 SkillCommand::List => {
40 for name in crate::embedded::skill_names() {
41 output::line(name);
42 }
43 Ok(())
44 }
45 SkillCommand::Show(show) => {
46 let Some(text) = crate::embedded::skill(&show.name) else {
47 return Err(AppError::Usage(format!("no such skill: {}", show.name)));
48 };
49 output::line(text.trim_end_matches('\n'));
50 Ok(())
51 }
52 SkillCommand::Install(install) => {
53 let roots = roots(install.agent)?;
54 let lines = skill_installer::install(&roots, install.apply, install.force)?;
55 for line in lines {
56 output::line(line);
57 }
58 Ok(())
59 }
60 SkillCommand::Uninstall(uninstall) => {
61 let roots = roots(uninstall.agent)?;
62 let lines = skill_installer::uninstall(&roots, uninstall.apply)?;
63 for line in lines {
64 output::line(line);
65 }
66 Ok(())
67 }
68 }
69}