Skip to main content

agent_exec/
install_skills.rs

1//! Implementation of the `install-skills` subcommand.
2//!
3//! Installs the built-in `agent-exec` skill into `.agents/skills/` or
4//! `.claude/skills/` and updates `.skill-lock.json`.
5
6use anyhow::Result;
7
8use crate::schema::{InstallSkillsData, InstalledSkillSummary, Response};
9use crate::skills::{LockEntry, LockFile, install_builtin, now_rfc3339, resolve_root_dir};
10
11/// Options for the `install-skills` subcommand.
12pub struct InstallSkillsOpts {
13    /// If true, install into the home directory; otherwise into cwd.
14    pub global: bool,
15    /// If true, use `.claude` root instead of `.agents`.
16    pub claude: bool,
17}
18
19/// Execute the `install-skills` command.
20///
21/// Prints a single JSON response to stdout on success.
22/// Returns an error on failure (caller maps to `ErrorResponse`).
23pub fn execute(opts: InstallSkillsOpts) -> Result<()> {
24    let root_dir = resolve_root_dir(opts.global, opts.claude)?;
25
26    let installed = install_builtin(&root_dir)?;
27
28    let lock_path = root_dir.join(".skill-lock.json");
29    let mut lock = LockFile::read(&lock_path)?;
30    let entry = LockEntry {
31        name: installed.name.clone(),
32        source_type: installed.source_type.clone(),
33        installed_at: now_rfc3339(),
34        path: installed.path.to_string_lossy().into_owned(),
35    };
36    lock.upsert(entry);
37    lock.write(&lock_path)?;
38
39    // Build and print the response.
40    let data = InstallSkillsData {
41        skills: vec![InstalledSkillSummary {
42            name: installed.name,
43            source_type: installed.source_type,
44            path: installed.path.to_string_lossy().into_owned(),
45        }],
46        global: opts.global,
47        lock_file_path: lock_path.to_string_lossy().into_owned(),
48    };
49    Response::new("install_skills", data).print();
50    Ok(())
51}