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, Layout};
16
17fn home() -> Result<Utf8PathBuf, AppError> {
18 std::env::var("HOME")
19 .ok()
20 .filter(|home| !home.is_empty())
21 .map(Utf8PathBuf::from)
22 .ok_or_else(|| AppError::Usage("HOME is not set".to_string()))
23}
24
25const SHARED_ROOT: &str = ".local/state/spec-driven-docs/skills/shared";
33
34fn layout(agent: Agent) -> Result<Layout, AppError> {
36 let home = home()?;
37 Ok(Layout {
38 roots: roots(&home, agent),
39 every_root: roots(&home, Agent::All),
40 shared: home.join(SHARED_ROOT),
41 record: home.join(RECORD_PATH),
42 })
43}
44
45fn roots(home: &Utf8Path, agent: Agent) -> Vec<Utf8PathBuf> {
46 let mut roots = Vec::new();
47 if matches!(agent, Agent::Codex | Agent::All) {
48 roots.push(home.join(".agents/skills"));
49 }
50 if matches!(agent, Agent::Claude | Agent::All) {
51 roots.push(home.join(".claude/skills"));
52 }
53 roots
54}
55
56pub fn run(_ctx: &AppContext, args: SkillArgs) -> Result<(), AppError> {
63 match args.command {
64 SkillCommand::List => {
65 for name in crate::embedded::skill_names() {
66 output::line(name);
67 }
68 Ok(())
69 }
70 SkillCommand::Show(show) => {
71 let Some(text) = crate::embedded::skill(&show.name) else {
72 return Err(AppError::Usage(format!("no such skill: {}", show.name)));
73 };
74 output::line(text.trim_end_matches('\n'));
75 Ok(())
76 }
77 SkillCommand::Install(install) => {
78 let layout = layout(install.agent)?;
79 let lines = skill_installer::install(&layout, install.apply, install.force)?;
80 for line in lines {
81 output::line(line);
82 }
83 Ok(())
84 }
85 SkillCommand::Uninstall(uninstall) => {
86 let layout = layout(uninstall.agent)?;
87 let lines = skill_installer::uninstall(&layout, uninstall.apply)?;
88 for line in lines {
89 output::line(line);
90 }
91 Ok(())
92 }
93 }
94}