skills

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 22, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Overview

Package skills reads and manages the agent skills installed on this machine.

A skill is a directory holding a SKILL.md whose YAML frontmatter names and describes it. Every agent that supports skills uses that same shape, and none of them expose skills over ACP — so opentree discovers them by walking directories, and can add, copy, and remove them the same way.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Bridge

func Bridge(repoRoot string) ([]string, error)

Bridge points every agent's repository skills tree at the one this repository actually has, and reports the trees it created.

Which agent reads which repository tree is not symmetric. opencode reads .claude/skills and .agents/skills as well as its own; Claude Code reads only .claude/skills. So a project skill kept under .opencode/skills is one the list shows, that opentree links into every worktree, and that Claude Code working there still cannot use.

A relative link, so it means the same thing from a worktree and survives being committed: a repository that tracks its skills can track the bridge with them, and then opentree has nothing left to do.

Not automatic, unlike Link. A worktree is opentree's own directory and linking inside it is housekeeping; the repository root is the user's, and a new directory there is theirs to ask for.

func Clone

func Clone(url string, dirs ...string) error

Clone installs a skill from a git repository into each of the given trees — the other way a skill arrives, alongside copying one that is already here.

The .git directory is kept, so `git -C <dir> pull` updates the skill later. opentree records no provenance of its own and there is no registry to re-fetch from; the clone is the only thing that remembers where the skill came from.

Cloned once and copied into the rest, .git and all, so each tree's copy stays independently pullable without asking the remote the same question once per agent.

func CloneName

func CloneName(url string) string

cloneName is the directory a URL clones into: its last path element, without the .git suffix. Anything hidden or with no name at all is refused rather than guessed at — the name becomes a directory under the user's skills.

func CopyTo

func CopyTo(s Skill, dirs ...string) error

CopyTo installs a copy of a skill into each of the given trees — the way a skill written for one agent reaches another, since they all read the same format.

A tree that refuses the copy does not cost the others theirs: the errors are collected and every remaining tree is still tried. Nothing here is a step in one operation; each is its own agent getting its own copy.

func Delete

func Delete(list ...Skill) error

Delete removes each skill's directory.

Several at once because one skill commonly sits in several trees, and taking it out of two of the three is an ordinary thing to want. A directory that refuses does not cost the others theirs.

func ExpandUserDir

func ExpandUserDir(path string) string

ExpandUserDir resolves a registry skills path against the user's home.

XDG_CONFIG_HOME is honoured for the ~/.config prefix because opencode reads it: a user who moved their config would otherwise be shown an empty list and told they have no skills.

func Install

func Install(ctx context.Context, e Entry, dirs ...string) error

Install downloads one entry into its own directory under each of the given trees.

Fetched once and copied into the rest: agents read each other's directories unevenly, so covering all of them takes more than one tree — and the same artifact pulled once per tree is the same bytes over the wire as many times as there are agents. Each copy carries the source file with it, so all of them can be updated later.

func Link(repoRoot, worktreePath string) ([]string, error)

Link points a worktree at the repository's skills and reports which trees it linked. Doing nothing is the common, correct outcome: a repository with no skills has nothing to share, and one that commits them has already had them checked out by git.

A symlink rather than a copy, because a copy starts drifting the moment either side is edited, and a project skill is one thing rather than one per branch. Editing it from inside a worktree edits the repository's copy, which is what a shared skill means.

func Missing

func Missing(repoRoot, worktreePath string) []string

Missing reports the repository skill trees a worktree cannot see — what the list warns about and what relinking would repair.

Stat rather than Lstat here, so a link left dangling by a deleted or renamed skills directory counts as missing. It is a link that resolves to nothing, and the agent behind it sees no skills at all.

func OverrideFile

func OverrideFile(spec config.SkillsSpec, repoRoot, skill string) string

OverrideFile is where a change to a skill's state should be written: the file already carrying an override for it, and otherwise the first one the agent reads.

Preferring the file that already holds the override is what makes the write take effect. Writing "on" into a higher-precedence file while a lower one still says "off" only works if the agent layers them that way, and the one thing observation proves is that an override written where the others live is honoured.

func Probe

func Probe(ctx context.Context, agent config.PredefinedAgent, cwd string) (map[string]bool, error)

Probe starts the agent, opens a session, and reports the commands it advertises, keyed by name.

It costs a real session in the agent's own storage — commands are a session update, so there is no way to be told without starting one. An empty conversation is cheap and the agent's own session list is full of them; the alternative is not being able to check at all.

func SetDisabled

func SetDisabled(spec config.SkillsSpec, repoRoot, skill string, state State) (string, error)

SetDisabled switches a skill off for an agent that keeps a list of the disabled rather than a map of states, or takes it off that list again.

A list carries less than a map: a name is on it or it is not, so off and default are the only two things it can say. Anything else is refused rather than approximated into the nearest thing the list can hold.

Turning a skill back on clears it from every file that names it, not only from the one an addition would go to. The files union when read — a name missing from one says nothing about the others — so clearing the repository's while the user's still lists it would report a skill as on that the agent goes on ignoring.

func SetOverride

func SetOverride(spec config.SkillsSpec, repoRoot, skill string, state State) (string, error)

SetOverride records a skill's state in the agent's settings, or clears the override when state is empty.

Clearing rather than writing "on" is the way back to the default, because "on" is not always the default: a skill whose own frontmatter asks not to be model-invoked would be quietly promoted to fully automatic by an explicit "on", which is not what turning it back on means.

Only the overrides object is rewritten. The rest of the file keeps its bytes, including the order of keys around it — a settings file is hand-maintained and frequently version-controlled, and reformatting all of it to change one entry is not a change the user asked for.

func SetState

func SetState(spec config.SkillsSpec, repoRoot, skill string, state State) (string, error)

SetState switches a skill off for one agent, or clears what switched it off, by whichever mechanism that agent has. Callers should not have to know which shape a given agent keeps its answer in.

func Update

func Update(ctx context.Context, dir string) (bool, error)

Update re-checks an installed skill against the site that published it, and replaces it when the published bytes have changed. It reports whether anything changed.

The digest is what makes this cheap: an unchanged skill is settled by the index alone, with nothing downloaded and nothing on disk touched. That is the check a clone cannot do — git has to fetch before it can tell.

Types

type Entry

type Entry struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	Type        string `json:"type"`
	URL         string `json:"url"`
	Digest      string `json:"digest"`
	// contains filtered or unexported fields
}

An Entry is one skill a site offers. Description is carried so the picker can show what a skill is for before it is on disk — the same line the agent will later match against.

func Discover

func Discover(ctx context.Context, site string) ([]Entry, error)

Discover asks a site what skills it publishes.

The argument is a site, not a URL to the index: the well-known path is fixed by the spec, so anything the user pastes — a bare host, a docs page deep in the tree — is reduced to its origin.

type Scope

type Scope int

Scope is where a skill lives, which decides who can see it.

const (
	// ScopeUser is the machine-wide tree, available in every repository.
	ScopeUser Scope = iota
	// ScopeRepo is this repository's own tree — the one a fresh worktree misses
	// unless the skills are committed.
	ScopeRepo
)

func (Scope) String

func (s Scope) String() string

type Skill

type Skill struct {
	Name        string // frontmatter name, falling back to the directory name
	Description string
	Dir         string // the directory holding SKILL.md
	// Agents are every registry agent that reads this skill's tree, in registry
	// order. Usually more than one: agents auto-load each other's directories,
	// so a skill installed once is commonly usable from all of them.
	Agents []string
	Scope  Scope
	// States is each agent's effective availability, keyed by agent name. An
	// agent reading the same file can still have been told to ignore it, so
	// this is per agent rather than per skill.
	States map[string]State
	// ManualOnly records the skill's own request not to be model-invoked,
	// before any override. Kept apart from States so clearing an override
	// returns the skill to what it asked for rather than to fully automatic.
	ManualOnly bool
}

Skill is one SKILL.md and the directory holding it.

func Scan

func Scan(repoRoot string) []Skill

Scan finds every skill opentree can see, once each.

Trees overlap by symlink as well as by configuration: a skill installer may keep the real directory in one tree and link it into another, so the same SKILL.md is reachable by two paths. Those are one skill readable by both agents, not two — collapsing them on the resolved path is what keeps the list honest, and what leaves the "duplicate" tag meaning a genuine conflict between two different files.

The surviving entry is the resolved directory, so editing and deleting act on the real skill rather than on a link to it.

Ordering is by name then directory, so the listing is stable and two same-named skills sit together where the difference is visible.

func (Skill) State

func (s Skill) State(agent string) State

State is the skill's availability to one agent, defaulting to on for an agent with no override mechanism.

type State

type State string

State is how available a skill is to one agent. The values are Claude Code's own, taken from the schema its binary carries for the overrides map.

const (
	// StateOn is the default: the agent loads it and may invoke it itself.
	StateOn State = "on"
	// StateNameOnly lists the skill without its description.
	StateNameOnly State = "name-only"
	// StateManualOnly loads the skill but leaves invoking it to the user.
	StateManualOnly State = "user-invocable-only"
	// StateOff means the agent does not load it at all.
	StateOff State = "off"
)

func (State) Label

func (s State) Label() string

Label is the short word for a row. On is the unremarkable case and says nothing, so a clean list stays clean.

type Tree

type Tree struct {
	Dir    string
	Scope  Scope
	Agents []string
	// Deep searches the tree at any depth rather than listing it one level
	// down. True only for a directory the user registered in their own config:
	// that is a place to look, and the agent reading it searches it for
	// **/SKILL.md. The standard trees are listed one level deep, because there
	// a nested SKILL.md is a skill's own reference material rather than a
	// second skill.
	Deep bool
}

Tree is one skills directory and the agents that read it.

Directories are the unit rather than agents because the mapping is many-to-many — opencode loads Claude Code's global tree as its own — and scanning per agent would read the same directory twice and report one skill as two.

func Trees

func Trees(repoRoot string) []Tree

Trees is every skills directory opentree knows about, each with its readers collected. An empty repoRoot yields only the machine-wide trees.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL