Skip to main content

spec_driven_docs/commands/
skill.rs

1//! `skill` subcommand: runtime-shape.
2//!
3//! Lists, prints, installs, and removes the embedded skills. Install and
4//! uninstall semantics live in `services::skill_installer`; this handler
5//! only resolves the home directory and the chosen roots.
6
7use 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
31/// List, print, install, or uninstall the embedded skills.
32///
33/// # Errors
34///
35/// [`AppError::Usage`] for an unknown skill or an unset home, and the
36/// installer's refusals and I/O errors.
37pub 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}