agent-skill-installer 0.1.0

Install an Agent Skill into the skill directories of AI coding tools, with consent and safe updates
Documentation

agent-skill-installer

crates.io docs.rs CI License: LGPL-3.0-or-later MSRV: 1.95

A Rust library that installs an Agent Skill shipped inside your program into the skill directories of the AI coding tools on the user's machine, so those tools know what your program is for and how to use it. It finds the tools that are installed, plans the fewest copies that reach all of them, installs only after the user agreed, and later keeps its own unchanged copies current without asking. The crate decides what would happen and carries it out; your program asks the user in its own CLI, TUI or GUI.

Features

  • Only where a tool is present. A tool counts as installed when its home directory exists, and a skill directory is only created inside such a home. The crate never creates a tool's home.
  • The fewest copies that reach every tool. The planner compares every combination of directories and picks the one that leaves the fewest tools seeing the skill twice, then needs the fewest copies, and names any tool that will see it twice anyway. Symlinks are resolved, so two names for one directory are one target.
  • Consent first. The first installation into a directory happens only after the user agreed. A "no" is kept in a file your program chooses, and that directory is not asked about again.
  • Silent updates of its own work only. The skill's frontmatter records which program installed it, the version and a digest over every file. A newer program updates an unchanged copy without asking, also inside a git working tree, which is reported with its root.
  • Never half-written. A new version is written beside the skills directory and renamed into place; on a failure the old one is put back.
  • Extensible. The built-in list covers six tools; a program can add its own tools and directories, and the planner treats them like the built-in ones.
  • Small. Depends on semver and sha2 only. No YAML parser, no network access, no terminal input or output.

Toolchain support

The minimum supported Rust version (MSRV) is 1.95. The crate targets a rolling window of stable Rust minus four releases, reviewed with each minor release. MSRV increases are made only in minor releases and noted in the changelog. CI builds and tests the crate against exactly the version in rust-version on every change.

Installation

cargo add agent-skill-installer

or in Cargo.toml:

[dependencies]
agent-skill-installer = "0.1"

Quick start

use agent_skill_installer::{AgentSkill, Program, Skill};

let skill = Skill::new(
    "devserial",
    "Serial terminal with a SQLite-buffered capture. Use it to read, search and write serial ports.",
    "# devserial\n\nRead the capture with `devserial read PORT --tail 50`.\n",
)?
.with_field("license", "GPL-3.0-or-later")?
.with_file("references/cli.md", "# Commands\n")?;
let program = Program::new("devserial", env!("CARGO_PKG_VERSION"))?;
let agent = AgentSkill::new(skill, program, "/path/in/your/data/dir/skill-consent")?;

// At every start: keep earlier installations current. Asks nobody.
agent.update()?;

// Once, when a place exists nobody has decided on: ask in your own surface.
if let Some(offer) = agent.offer()? {
    for step in offer.steps() {
        println!("{} (read by {})", step.place.display(), step.tools.join(", "));
    }
    let agreed = true; // the user's answer
    if agreed {
        offer.accept()?;
    } else {
        offer.decline()?; // these places are not offered again
    }
}
# Ok::<(), Box<dyn std::error::Error>>(())

A command line can show agent.status(), install with agent.install_everywhere(), which also goes where the user declined before, and take the skill out with agent.remove().

status writes nothing. It returns one step per place with what is there (Found) and what would happen (Action): install, update, nothing and why, or a problem to report. The Plan inside it lists the installed tools, the tools that will see the skill twice, and any tool that cannot be served, with the reason. A program that wants to decide place by place uses agent.installer(), whose assess and apply take an Approval of its own.

To add a tool the built-in list leaves out:

use agent_skill_installer::{Catalog, Location, SkillDir, Tool};

let mut catalog = Catalog::builtin();
catalog
    .add_dir(
        SkillDir::new("kiro", Location::home(".kiro/skills"))
            .requires(Location::home(".kiro")),
    )?
    .add_tool(Tool::new("kiro", "Kiro", Location::home(".kiro")).reads(["kiro"]))?;
# Ok::<(), agent_skill_installer::CatalogError>(())

AgentSkill::with_catalog(catalog) then uses it.

Consent and updates

Consent is given for a place, not for a version. What assess decides for a place depends on what it finds there under the skill's name:

Found Action
Nothing, the place is planned Install once the user approves (Action::Install)
Nothing, the user declined the place Nothing (Why::Declined)
This program's copy, unchanged, older Update without asking (Action::Update)
This program's copy, unchanged, same or newer Nothing; an older program never downgrades
This program's copy, changed by somebody Reported (Problem::Modified); replaced only when listed in Approval::overwrite
A skill of that name that is not this program's Reported (Problem::Foreign), never replaced

A copy counts as this program's when its metadata block carries the record the crate wrote, in the shape the crate writes it:

metadata:
  installed-by: "devserial"
  installed-version: "0.3.0"
  content-digest: "sha256:…"

The digest covers every file of the skill directory with its own value blanked. Any edited, added or removed file makes the copy "changed"; .DS_Store and Thumbs.db do not. Copies of this program in places the plan no longer chooses, for example for a tool uninstalled since, are still updated and removed.

Supported tools

The built-in catalog, from docs/tools.md. "Measured" means the behaviour was observed with the tool on a test machine; "documented" means it follows the vendor's documentation only.

Tool Installed when this exists Skill directories it reads Evidence
Claude Code ~/.claude ($CLAUDE_CONFIG_DIR when set) ~/.claude/skills ($CLAUDE_CONFIG_DIR/skills) measured
Codex ~/.codex ($CODEX_HOME when set) ~/.agents/skills documented
Gemini CLI ~/.gemini ~/.gemini/skills, ~/.agents/skills measured
GitHub Copilot ~/.copilot ($COPILOT_HOME when set) ~/.copilot/skills ($COPILOT_HOME/skills), ~/.agents/skills; in VS Code also ~/.claude/skills documented
Cursor ~/.cursor ~/.cursor/skills, ~/.agents/skills, ~/.claude/skills documented
OpenCode ~/.config/opencode ($XDG_CONFIG_HOME/opencode when set) ~/.config/opencode/skills ($XDG_CONFIG_HOME/opencode/skills), ~/.claude/skills, ~/.agents/skills documented; XDG_CONFIG_HOME from secondary sources

Claude Code reads only ~/.claude/skills, and Gemini CLI does not read it, so a machine with both needs two copies. With Claude Code and Codex installed, Cursor, OpenCode and Copilot in VS Code read both copies when they are installed too; the plan names them in Plan::duplicated.

Limits

  • Windows paths follow the tools' documentation, which only ever says ~. The test suite runs on Windows in CI, but no tool's behaviour was measured there, and the symlink cases are tested on Unix only.
  • Four of the six tools are documented, not measured: Codex, Copilot, Cursor and OpenCode.
  • Two programs writing the same skill at the same moment are not coordinated. Each write is atomic, so the place ends up with one complete version, but one of the two may report a failure.
  • An interrupted process can leave a .asi-* staging directory beside the skills directory. The next write of the same skill removes it once it is an hour old; a younger one may belong to a write in progress.
  • Package managers do not install skills. That would be installing without consent; the program asks at runtime instead.

Contributing

See CONTRIBUTING.md for the workflow and the checks CI runs, and SECURITY.md for reporting a vulnerability.

License

LGPL-3.0-or-later. See LICENSE.