Skip to main content

Manager

Struct Manager 

Source
pub struct Manager { /* private fields */ }
Expand description

Skill manager: carries injectable context and runs add/list/remove/update.

This is the high-level entry point for library consumers. It resolves an Env (home / config / cwd) once at construction, then every operation is a plain method taking a request struct and returning a structured outcome.

§Examples

use agents_skills::{AddRequest, Manager};

// Real environment:
let real = Manager::new();

// Or a sandboxed environment (no side effects outside the given paths):
let sandboxed = Manager::builder()
    .home("/tmp/home")
    .config("/tmp/config")
    .cwd("/tmp/project")
    .build();

let req = AddRequest::new("anthropics/skills");
let _ = (real, sandboxed, req);

Implementations§

Source§

impl Manager

Source

pub fn new() -> Self

Build a manager from the real environment (home / config / cwd).

Equivalent to Manager::builder().build().

Source

pub fn builder() -> ManagerBuilder

Start customizing a manager (inject home/config/cwd/env vars).

§Examples
use agents_skills::Manager;

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

pub fn env(&self) -> &Env

Access the resolved environment context.

Source

pub fn add(&self, req: &AddRequest) -> Result<AddOutcome>

Add (install) skills from a source.

Parses the source, discovers its skills, and installs each selected skill into the canonical dir (the only place real files live), recording successful installs in the lockfile. Returns a structured AddOutcome with discovered, selected, installed and failed skills.

add never links any agent: use Manager::agent to expose the canonical dir to an agent afterwards.

§Selection defaults
  • skills empty → all discovered skills; a "*" entry → all as well.
  • list_only → discover and report, without installing anything.
§Examples

Install a local skill into a scratch environment (hermetic — no network, no real home access):

use agents_skills::{AddRequest, Manager};

let tmp = tempfile::TempDir::new().unwrap();
let src = tmp.path().join("hello");
std::fs::create_dir_all(&src).unwrap();
std::fs::write(
    src.join("SKILL.md"),
    "---\nname: hello\ndescription: says hello\n---\n\n# hello\n",
)
.unwrap();

let manager = Manager::builder()
    .home(tmp.path().join("home"))
    .config(tmp.path().join("config"))
    .cwd(tmp.path().join("project"))
    .build();

let outcome = manager.add(&AddRequest::new(src.display().to_string()))?;
assert!(!outcome.installed.is_empty());
§Errors
Source

pub fn agent(&self, req: &AgentRequest) -> Result<AgentOutcome>

Link or unlink agents’ skills dirs relative to the canonical dir.

Connects each agent’s own skills dir to the canonical dir with a directory-level symlink, so every install/update/remove is immediately visible to all linked agents. With req.unlink, disconnects those dirs instead — removes the symlink (only when it points at the canonical dir) and restores any parked backup content into a real dir; the canonical dir and its skills are left untouched.

Pre-existing content is never destroyed. When linking, every entry of the agent dir that does not go into the canonical dir is parked in a backup slot (<base>/.agents/backup-skills/<agent>); unlink restores it. With req.migrate, skill subdirs are moved into the canonical dir instead — name clashes keep the canonical copy, and names disabled in the disabled-skills dir stay disabled (the agent-side copy is parked, reported via LinkOutcome::Migrated skipped) — and only non-skill entries are parked. Rerunning with migrate on an already linked agent pulls parked skills out of the backup slot. Legacy per-skill symlinks pointing into the canonical dir are taken over automatically. Linking is refused only when the agent dir is a foreign symlink or a stale non-empty backup slot exists.

Universal agents (whose skills dir already is the canonical dir) report LinkOutcome::AlreadyLinked. Agents whose root dir does not exist in this scope are reported as LinkOutcome::Skipped (except claude-code, the historical exception).

§Selection defaults
  • agents empty → auto-detect installed agents (plus the universal agents); a "*" entry → every known agent.
§Errors

SkillsError::InvalidAgents when agents names an unknown agent.

Source

pub fn agent_status(&self, global: bool) -> Vec<AgentStatus>

Link status of every installed agent in this scope.

Only agents detected as installed locally (or already linked) are reported. Agents that natively read the canonical dir (universal) report canonical; agents connected via a directory-level symlink report linked.

For unlinked, non-canonical agents the status classifies the agent dir’s private content (internal_skills / internal_others, the same rules link and migrate use) and reports a pending backup slot (pending_backup) when one is waiting to be restored by unlink.

Ordering: agents that natively use the canonical dir (canonical: true) come first, then the remaining agents — both groups keep the static agent table order. This is the exact order agent --status renders; callers do not need to sort again.

Source

pub fn list(&self, req: &ListRequest) -> Result<Vec<ListedSkill>>

List installed skills (project or global), enriched with lock metadata.

Scans the canonical skills directory and joins each entry with its lockfile record, producing serde-serializable ListedSkill values — the same shape emitted by list --json.

§Examples
use agents_skills::{ListRequest, Manager};

let manager = Manager::new();
let skills = manager.list(&ListRequest::default())?;
for skill in skills {
    println!("{} -> {}", skill.name, skill.path.display());
}
§Errors

SkillsError::InvalidAgents when agents names an unknown agent.

Source

pub fn disable(&self, req: &DisableRequest) -> Result<DisableOutcome>

Disable installed skills.

Moves each selected skill’s directory from the canonical dir into the sibling disabled-skills dir, hiding it from every linked or universal agent at once. Files are preserved, so Manager::enable restores them losslessly; the lockfile entry is kept, so list still shows the skill’s source metadata.

§Selection semantics
  • skills empty and all false → nothing is disabled; the outcome reports the currently enabled names (used by the CLI to print a hint).
  • all true → every currently enabled skill.
§Examples

Disable an installed skill in a scratch environment (hermetic — no real home access):

use agents_skills::{DisableRequest, Manager};

let tmp = tempfile::TempDir::new().unwrap();
// Simulate an installed skill in the canonical dir.
let skill_dir = tmp.path().join("project/.agents/skills/pdf");
std::fs::create_dir_all(&skill_dir).unwrap();
std::fs::write(
    skill_dir.join("SKILL.md"),
    "---\nname: pdf\ndescription: pdf tools\n---\n\n# pdf\n",
)
.unwrap();

let manager = Manager::builder()
    .home(tmp.path().join("home"))
    .config(tmp.path().join("config"))
    .cwd(tmp.path().join("project"))
    .build();

let outcome = manager.disable(&DisableRequest {
    skills: vec!["pdf".into()],
    ..Default::default()
})?;
assert_eq!(outcome.disabled, vec!["pdf".to_string()]);
§Errors

SkillsError::Io if a directory move fails.

Source

pub fn enable(&self, req: &EnableRequest) -> Result<EnableOutcome>

Enable previously disabled skills.

Moves each selected skill’s directory from the disabled-skills dir back into the canonical dir, restoring its visibility to every linked or universal agent. This is the exact inverse of Manager::disable.

§Selection semantics
  • skills empty and all false → nothing is enabled; the outcome reports the currently disabled names (used by the CLI to print a hint).
  • all true → every currently disabled skill.
§Examples

Re-enable a disabled skill in a scratch environment (hermetic — no real home access):

use agents_skills::{EnableRequest, Manager};

let tmp = tempfile::TempDir::new().unwrap();
// Simulate a disabled skill parked in the disabled-skills dir.
let skill_dir = tmp.path().join("project/.agents/disabled-skills/pdf");
std::fs::create_dir_all(&skill_dir).unwrap();
std::fs::write(
    skill_dir.join("SKILL.md"),
    "---\nname: pdf\ndescription: pdf tools\n---\n\n# pdf\n",
)
.unwrap();

let manager = Manager::builder()
    .home(tmp.path().join("home"))
    .config(tmp.path().join("config"))
    .cwd(tmp.path().join("project"))
    .build();

let outcome = manager.enable(&EnableRequest {
    skills: vec!["pdf".into()],
    ..Default::default()
})?;
assert_eq!(outcome.enabled, vec!["pdf".to_string()]);
§Errors

SkillsError::Io if a directory move fails.

Source

pub fn remove(&self, req: &RemoveRequest) -> Result<RemoveOutcome>

Remove installed skills.

Deletes each skill’s directory from the canonical dir and drops its lockfile entry. Removal applies to every linked agent at once (they all share the canonical dir); agent links themselves are untouched — call Manager::agent with unlink: true to disconnect an agent instead.

§Selection semantics
  • skills empty and all false → nothing is removed; the outcome reports the currently enabled names (used by the CLI to print a hint).
  • all true → every installed skill (enabled or disabled) plus every lockfile key.
§Examples
use agents_skills::{Manager, RemoveRequest};

let tmp = tempfile::TempDir::new().unwrap();
let manager = Manager::builder()
    .home(tmp.path().join("home"))
    .cwd(tmp.path().join("project"))
    .build();

let req = RemoveRequest {
    skills: vec!["pdf".to_string()],
    ..Default::default()
};
// Nothing installed in the scratch dir, so this is a harmless no-op.
let outcome = manager.remove(&req)?;
assert!(outcome.removed.is_empty());
Source

pub fn update(&self, req: &UpdateRequest) -> Result<UpdateOutcome>

Update installed skills from their recorded (non-local) sources.

Reads the lockfile, re-clones each recorded source once (skills sharing a source are grouped), re-installs the latest version into the canonical dir (all linked agents see the update immediately), and reports per-skill success/failure counts. Locally-sourced skills are skipped.

§Scope resolution

UpdateRequest::scope is Scope::Auto by default: project scope if the project has skills or a lockfile, otherwise global.

§Examples
use agents_skills::{Manager, UpdateRequest};

let tmp = tempfile::TempDir::new().unwrap();
let manager = Manager::builder()
    .home(tmp.path().join("home"))
    .cwd(tmp.path().join("project"))
    .build();

// No lockfile in the scratch dir, so nothing to update.
let outcome = manager.update(&UpdateRequest::default())?;
assert_eq!(outcome.updated, 0);
§Errors

SkillsError::Message when a recorded source fails to re-parse. Per-skill clone/install failures are captured in UpdateOutcome::failures rather than returned as errors.

Trait Implementations§

Source§

impl Default for Manager

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.