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
impl Manager
Sourcepub fn new() -> Self
pub fn new() -> Self
Build a manager from the real environment (home / config / cwd).
Equivalent to Manager::builder().build().
Sourcepub fn builder() -> ManagerBuilder
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();Sourcepub fn add(&self, req: &AddRequest) -> Result<AddOutcome>
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
skillsempty → 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
SkillsError::Messagewhen the source is invalid, unreadable, or contains no valid skill (aSKILL.mdwithnameanddescription).SkillsError::Git,SkillsError::Http,SkillsError::Io,SkillsError::Zip, etc. for transport and filesystem failures.
Sourcepub fn agent(&self, req: &AgentRequest) -> Result<AgentOutcome>
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
agentsempty → auto-detect installed agents (plus the universal agents); a"*"entry → every known agent.
§Errors
SkillsError::InvalidAgents when agents names an unknown agent.
Sourcepub fn agent_status(&self, global: bool) -> Vec<AgentStatus>
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.
Sourcepub fn list(&self, req: &ListRequest) -> Result<Vec<ListedSkill>>
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.
Sourcepub fn disable(&self, req: &DisableRequest) -> Result<DisableOutcome>
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
skillsempty andallfalse → nothing is disabled; the outcome reports the currently enabled names (used by the CLI to print a hint).alltrue → 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.
Sourcepub fn enable(&self, req: &EnableRequest) -> Result<EnableOutcome>
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
skillsempty andallfalse → nothing is enabled; the outcome reports the currently disabled names (used by the CLI to print a hint).alltrue → 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.
Sourcepub fn remove(&self, req: &RemoveRequest) -> Result<RemoveOutcome>
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
skillsempty andallfalse → nothing is removed; the outcome reports the currently enabled names (used by the CLI to print a hint).alltrue → 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());Sourcepub fn update(&self, req: &UpdateRequest) -> Result<UpdateOutcome>
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.