Skip to main content

agent_exec/
install_skills.rs

1//! Implementation of the `install-skills` subcommand.
2//!
3//! Installs one or more skills into `.agents/skills/` and updates
4//! `.agents/.skill-lock.json`.
5
6use anyhow::Result;
7
8use crate::schema::{InstallSkillsData, InstalledSkillSummary, Response};
9use crate::skills::{LockEntry, LockFile, Source, install, now_rfc3339, resolve_agents_dir};
10
11/// Options for the `install-skills` subcommand.
12pub struct InstallSkillsOpts<'a> {
13    /// Source specification (e.g. `"self"` or `"local:/path/to/skill"`).
14    pub source: &'a str,
15    /// If true, install into `~/.agents/`; otherwise install into `./.agents/`.
16    pub global: 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    // Parse the source.
25    let source = Source::parse(opts.source)?;
26
27    // Resolve the `.agents/` directory.
28    let agents_dir = resolve_agents_dir(opts.global)?;
29
30    // Install the skill.
31    let installed = install(&source, &agents_dir)?;
32
33    // Read and update the lock file.
34    let lock_path = agents_dir.join(".skill-lock.json");
35    let mut lock = LockFile::read(&lock_path)?;
36    let entry = LockEntry {
37        name: installed.name.clone(),
38        source_type: installed.source_str.clone(),
39        installed_at: now_rfc3339(),
40        path: installed.path.to_string_lossy().into_owned(),
41    };
42    lock.upsert(entry);
43    lock.write(&lock_path)?;
44
45    // Build and print the response.
46    let data = InstallSkillsData {
47        skills: vec![InstalledSkillSummary {
48            name: installed.name,
49            source_type: installed.source_str,
50            path: installed.path.to_string_lossy().into_owned(),
51        }],
52        global: opts.global,
53        lock_file_path: lock_path.to_string_lossy().into_owned(),
54    };
55    Response::new("install_skills", data).print();
56    Ok(())
57}