spec_driven_docs/commands/
skill.rs1use camino::{Utf8Path, Utf8PathBuf};
9
10use crate::cli::skill::{Agent, SkillArgs, SkillCommand};
11use crate::context::AppContext;
12use crate::domain::skill_record::RECORD_PATH;
13use crate::error::AppError;
14use crate::output;
15use crate::services::skill_installer::{self, AGENTS_ROOT, CLAUDE_ROOT, Layout, SHARED_ROOT, home};
16
17fn layout(agent: Agent) -> Result<Layout, AppError> {
19 let home = home()?;
20 Ok(Layout {
21 roots: roots(&home, agent),
22 every_root: roots(&home, Agent::All),
23 shared: home.join(SHARED_ROOT),
24 record: home.join(RECORD_PATH),
25 })
26}
27
28fn roots(home: &Utf8Path, agent: Agent) -> Vec<Utf8PathBuf> {
29 let mut roots = Vec::new();
30 if matches!(agent, Agent::Codex | Agent::All) {
31 roots.push(home.join(AGENTS_ROOT));
32 }
33 if matches!(agent, Agent::Claude | Agent::All) {
34 roots.push(home.join(CLAUDE_ROOT));
35 }
36 roots
37}
38
39pub fn run(_ctx: &AppContext, args: SkillArgs) -> Result<(), AppError> {
46 match args.command {
47 SkillCommand::List => {
48 for name in crate::embedded::skill_names() {
49 output::line(name);
50 }
51 Ok(())
52 }
53 SkillCommand::Show(show) => {
54 let Some(text) = crate::embedded::skill(&show.name) else {
55 return Err(AppError::Usage(format!("no such skill: {}", show.name)));
56 };
57 output::line(text.trim_end_matches('\n'));
58 Ok(())
59 }
60 SkillCommand::Install(install) => {
61 let layout = layout(install.agent)?;
62 let lines = skill_installer::install(&layout, install.apply, install.force)?;
63 for line in lines {
64 output::line(line);
65 }
66 Ok(())
67 }
68 SkillCommand::Uninstall(uninstall) => {
69 let layout = layout(uninstall.agent)?;
70 let lines = skill_installer::uninstall(&layout, uninstall.apply)?;
71 for line in lines {
72 output::line(line);
73 }
74 Ok(())
75 }
76 }
77}