agents-skills 0.1.0

A minimal, stable, easy-to-understand skill installer and manager for AI agents
Documentation

agents-skills

crates.io docs.rs CI License

A minimal, stable Rust library for installing and managing AI agent skills, with an optional command-line interface built on top.

agents-skills is library first: import it into your Rust project to install, list, remove, and update SKILL.md packages for Claude Code, Codex, Cursor, and 70+ other coding agents. A small CLI (agents-skills) ships alongside, implemented as a thin rendering layer over the exact same public API.

See also: 中文 README

Why a library?

  • Embed skill management into your own tools — a plugin manager, an agent launcher, or a build script can install skills without shelling out to a binary.
  • Pure data, no side effects on stdout — every API returns structured results and surfaces errors via Result; it never prints and never calls process::exit. You decide how to render and when to exit.
  • Injectable contextManagerBuilder lets you point at any home/config/cwd, making tests and sandboxes trivial.

Getting started

Add the dependency to your Cargo.toml:

[dependencies]
agents-skills = "1"

Install and list skills with the high-level Manager facade:

use agents_skills::{AddRequest, ListRequest, Manager, Result};

fn main() -> Result<()> {
    let manager = Manager::new();

    // Shortcut: install every skill from a source with default options.
    let outcome = manager.add_source("anthropics/skills")?;
    println!("installed {} skill(s)", outcome.installed.len());

    // Full form: install to specific agents (remaining fields default).
    let outcome = manager.add(&AddRequest {
        source: "anthropics/skills".to_string(),
        agents: vec!["*".to_string()],
        ..Default::default()
    })?;
    println!("installed {} skill(s)", outcome.installed.len());

    // List installed skills (serde-serializable; same shape as `list --json`).
    let skills = manager.list(&ListRequest::default())?;
    println!("{skills:?}");
    Ok(())
}

API

High-level: Manager

One-stop operations. Each takes a plain request struct and returns a structured outcome.

Method Request Returns
Manager::add AddRequest AddOutcome (installed + failed)
Manager::add_source impl Into<String> AddOutcome (installed + failed)
Manager::list ListRequest Vec<ListedSkill> (serde-serializable)
Manager::remove RemoveRequest RemoveOutcome (removed names)
Manager::update UpdateRequest UpdateOutcome (updated/failed counts)

Request structs are Default + Clone with builder-style field overrides; outcomes are plain data.

Injectable context: ManagerBuilder

use agents_skills::Manager;

let manager = Manager::builder()
    .home("/tmp/home")
    .config("/tmp/config")
    .cwd("/tmp/project")
    .env_var("CLAUDE_CONFIG_DIR", "/tmp/claude")
    .build();

Manager::new() is just Manager::builder().build() resolved against the real environment.

Low-level: core primitives

For finer control, the underlying core functions are re-exported at the crate root:

Examples

Run the bundled examples to see real usage:

cargo run --example manage      # add → list → remove on a scratch dir (no side effects)
cargo run --example add_skill   # install via Manager into your real environment

Features

  • Install from anywhere — local paths, GitHub repos/URLs, GitLab, SSH/git URLs, and arbitrary HTTPS endpoints (well-known discovery or direct download).
  • 70+ agents — static directory-mapping table, data-driven and dependency-injectable for testability.
  • Project and global scopes — install to .agents/skills (project) or ~/.agents/skills (global) with symlink or copy mode.
  • Lockfileskills-lock.json records the source and a SHA-256 content hash for every installed skill, enabling reproducible update.
  • Skill discovery — priority container dirs (skills/, .curated/, .experimental/, .system/) with shallow-shadowing-deep resolution.
  • Cross-platform — macOS, Linux, and Windows (directory symlinks on Windows, git2 for transport-agnostic cloning).

Source formats

The source field of AddRequest (and the CLI <source> argument) accepts:

Format Example
Local path ./my-skill, /abs/path/skill
GitHub shorthand owner/repo, owner/repo@skill, owner/repo/subpath
GitHub URL https://github.com/owner/repo, .../tree/main/skills
GitLab URL https://gitlab.com/group/repo, .../-/tree/main/skills
SSH / git URL git@github.com:owner/repo.git
HTTPS (well-known) https://example.com/skills (discovery → download fallback)
HTTPS (download) .../skill.zip, .../skill.tar.gz, raw SKILL.md

Install locations

  • Project scope./.agents/skills/<name> (canonical), symlinked into each agent's project skills directory.
  • Global scope~/.agents/skills/<name> (canonical), plus each agent's user-level skills directory.

Command-line interface

A small CLI ships on top of the library:

# Install (from crates.io)
cargo install agents-skills

# Install a skill from a GitHub repo
agents-skills add anthropics/skills

# Install a specific skill, to a specific agent
agents-skills add anthropics/skills@pdf --agent claude-code

# List as machine-readable JSON
agents-skills list --json

# Update everything from its lockfile source
agents-skills update
Command Aliases Description
add a, i, install Install skill packages from a source
remove rm, r Remove installed skills
list ls List installed skills
update upgrade, check Update skills to their latest versions

add

agents-skills add <source> [options]

Options:
  -g, --global        Install globally (user-level) instead of project-level
  -a, --agent <a>...  Agents to install to ('*' for all)
  -s, --skill <s>...  Skill names to install ('*' for all)
  -l, --list          List available skills without installing
      --copy          Copy files instead of symlinking
      --all           Shorthand for --skill '*' --agent '*' -y
      --full-depth    Search all subdirectories even with a root SKILL.md
  -y, --yes           Skip confirmation prompts

remove

agents-skills remove [skills...] [options]

Options:
  -g, --global        Remove from global scope instead of project scope
  -a, --agent <a>...  Remove from specific agents ('*' for all)
  -s, --skill <s>...  Skills to remove ('*' for all)
      --all           Shorthand for --skill '*' --agent '*' -y
  -y, --yes           Skip confirmation prompts

list

agents-skills list [options]

Options:
  -g, --global        List global skills (default: project)
  -a, --agent <a>...  Filter by specific agents
      --json          Output as JSON (machine-readable, no ANSI codes)

update

agents-skills update [skills...] [options]

Options:
  -g, --global        Update global skills only
  -p, --project       Update project skills only
  -y, --yes           Skip the scope prompt (auto-detect)

Project structure

src/
├── lib.rs              Library root: re-exports Manager + core primitives
├── manager.rs          High-level Manager facade (add/list/remove/update)
├── error.rs            Unified error type and Result alias
├── core/               Domain logic (pure functions, dependency-injectable)
│   ├── source.rs       Source string parsing
│   ├── agents.rs       Agent → skills directory mapping table
│   ├── discover.rs     SKILL.md discovery + frontmatter parsing
│   ├── fetch.rs        git clone / HTTP download / archive extraction
│   ├── install.rs      Install orchestration (canonical + symlink/copy)
│   └── lock.rs         skills-lock.json read/write + content hashing
├── main.rs             Bin entry point (thin CLI over the library)
├── cli.rs              clap command tree (commands, aliases, flags)
└── commands/           CLI rendering layer (arg unpacking + output only)
    ├── add.rs
    ├── remove.rs
    ├── list.rs
    └── update.rs

examples/
├── add_skill.rs        Install a skill via the Manager facade (real usage)
└── manage.rs           add → list → remove lifecycle on a scratch dir

tests/
├── common/mod.rs       Shared integration-test fixtures
├── lib_api.rs          Library API integration tests
├── cli_add.rs
├── cli_remove.rs
├── cli_list.rs
└── cli_version.rs

Development

cargo build            # build
cargo test             # run all tests (61 unit + 26 integration)
cargo run --example manage   # run a library usage example
cargo clippy           # lint
cargo fmt              # format

Tests follow the test pyramid: fast, isolated unit tests live inline in src/ via #[cfg(test)], while black-box integration tests in tests/ drive the real CLI through assert_cmd.

Design choices

The crate intentionally stays minimal and stable:

  • Library first — the library is the primary interface; the CLI is a thin rendering layer over the same public API.
  • Pure data — the library never prints and never calls process::exit; it returns structured outcomes and surfaces errors via Result.
  • No telemetry — nothing leaves your machine.

License

Licensed under either of:

at your option.