skills

package
v0.4.0 Latest Latest
Warning

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

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

Documentation

Overview

Package skills owns spec-compliant Agent Skills: discovery, parsing, and the in-memory registry the agent loop reads. A Skill is a reusable capability playbook the model invokes via the `Skill` tool (description-matched) or the user invokes via `/<skill-name>` slash.

Disk layout per skill (mirrors agentskills.io/specification):

skills/<slug>/
├── SKILL.md          required — frontmatter + body
├── scripts/          optional — executable, loaded on demand
├── references/       optional — docs loaded on demand
└── assets/           optional — templates, schemas

Progressive disclosure is the spec's load contract — only the metadata (name + description) is read into the system prompt at startup; the body loads when the skill is invoked; resources load when the model reads them via read_file or runs them via run_bash.

Index

Constants

View Source
const (
	OfficialSkillsOwner = "yottadynamics"
	OfficialSkillsRepo  = "yottacode-skills"
	OfficialPrefix      = "official/"
)

OfficialSkillsOwner and OfficialSkillsRepo identify the public curated skills catalog maintained by YottaDynamics. Paid/private skill packs intentionally live in separate authenticated repositories so this public source stays simple and redistributable.

View Source
const CurrentLockfileVersion = 1

CurrentLockfileVersion is the schema version this binary writes. Loaders accept any version <= this; newer files are still parsed but a warning is logged so users know their newer client wrote it.

View Source
const LockfileName = ".lock.json"

LockfileName is the on-disk filename inside DestRoot. Dot-prefixed so registry.loadDir's existing hidden-entry skip already excludes it from skill enumeration.

View Source
const OfficialCatalogCacheName = "catalog.json"

OfficialCatalogCacheName is the metadata cache file under UserSkillsDir(). It stores only safe browse metadata; installing still fetches real skill bytes from the yottacode-skills repository.

View Source
const TrustUnverified = "unverified"

TrustUnverified is the v2 trust tag. Every installed entry receives it; the field is reserved for a future signing/trust subsystem (Ed25519 verification, trusted-publisher allowlists) that can upgrade installed entries without rewriting the lockfile schema.

Variables

This section is empty.

Functions

func BuiltinResource

func BuiltinResource(slug, relPath string) ([]byte, error)

BuiltinResource reads a resource file (script, reference, asset) from inside a built-in skill's embedded directory. relPath is the resource's path relative to the skill's root (e.g. "references/playbook.md" or "scripts/check.sh"). Returns the bytes or an error if the resource isn't embedded. Disk-loaded skills do not flow through this path — their resources live on disk and are read by the model through read_file the normal way.

func HashInstalledDir

func HashInstalledDir(dir string) (string, error)

HashInstalledDir produces a stable "sha256:HEXLOWER" identifier for the on-disk skill at dir. The lockfile is excluded from the manifest (the lockfile sits one level up from per-skill dirs, but we belt-and-suspenders this so a future colocation never breaks the hash) and so are any dot-prefixed staging dirs that Install may have crashed mid-flight and left behind.

func NormalizeOfficialSource added in v0.4.0

func NormalizeOfficialSource(src string) (string, bool, error)

NormalizeOfficialSource expands the public official shortcut into the canonical GitHub shorthand. Non-official sources are returned unchanged so callers can pass every install source through this helper unconditionally.

func OfficialSource added in v0.4.0

func OfficialSource(slug string) (string, error)

OfficialSource returns the GitHub shorthand for a skill in the official public catalog. The returned string is accepted by the existing GitHub Contents installer, so official installs reuse the normal fetch/update path.

func ProjectSkillsDir

func ProjectSkillsDir(cwd string) string

ProjectSkillsDir returns the per-project skills dir: <cwd>/.yottacode/skills. Project-local definitions are checked in here so a team can ship a repo-specific skill alongside the codebase. Project wins on name collision with user and built-in.

func Uninstall

func Uninstall(name string) (string, error)

Uninstall removes a user-scope skill by name. Returns the path removed (for logging). Intentionally scoped to UserSkillsDir() — a project-scope skill is committed source, the user removes that via git/rm directly; a built-in is embedded in the binary and can't be removed at runtime.

func UserSkillsDir

func UserSkillsDir() (string, error)

UserSkillsDir returns the global skills dir: $YOTTACODE_HOME/skills (when the env var is set) or ~/.yottacode/skills otherwise — the shared ychome.Dir resolution, so all global state lives under the same root regardless of override.

Types

type AlreadyInstalledError added in v0.4.0

type AlreadyInstalledError struct {
	Name string
	Dir  string
}

AlreadyInstalledError reports that an install target already exists. CLI callers surface this as a hard error, while interactive Catalog installs can treat it as an idempotent no-op.

func IsAlreadyInstalled added in v0.4.0

func IsAlreadyInstalled(err error) (*AlreadyInstalledError, bool)

IsAlreadyInstalled reports whether err came from the install overwrite guard.

func (*AlreadyInstalledError) Error added in v0.4.0

func (e *AlreadyInstalledError) Error() string

type CheckOptions

type CheckOptions struct {
	DestRoot string
	Name     string
}

CheckOptions tunes Check. DestRoot defaults to UserSkillsDir; tests inject a tempdir. Name filters to a single skill when non-empty.

type CheckResult

type CheckResult struct {
	Name     string
	Status   CheckStatus
	Lock     *LockEntry // nil when missing-lock
	DiskHash string     // empty when missing-lock or orphaned-lock
	Dir      string     // absolute path under UserSkillsDir, even when missing
	Error    string     // populated for hash-error
}

CheckResult is one line of the `skills check` report.

func Check

func Check(opts CheckOptions) ([]CheckResult, error)

Check inspects every user-scope skill (or just the named one) and reports its drift status. The returned slice is name-sorted; an empty result means there are no user-installed skills at all (and no lockfile entries to compare against).

type CheckStatus

type CheckStatus string

CheckStatus enumerates the outcomes Check can report for one skill. Stable strings so CLI scripting can grep on them.

const (
	// CheckOK — disk hash matches the lockfile entry; nothing to do.
	CheckOK CheckStatus = "ok"
	// CheckModified — disk hash differs from the lockfile entry.
	// User edited the installed copy in place.
	CheckModified CheckStatus = "modified"
	// CheckMissingLock — the skill exists on disk but has no
	// lockfile entry (installed pre-Phase 2, or the lockfile was
	// deleted). Update can't refresh it; user must reinstall to
	// re-record provenance.
	CheckMissingLock CheckStatus = "missing-lock"
	// CheckOrphanedLock — the lockfile has an entry but the dir is
	// gone (someone `rm -rf`'d the install). Update can refetch from
	// the recorded source.
	CheckOrphanedLock CheckStatus = "orphaned-lock"
	// CheckHashError — couldn't compute the disk hash (permissions,
	// truncated file, etc.). Surfaced verbatim so the user can fix
	// the underlying problem.
	CheckHashError CheckStatus = "hash-error"
)

type InstallOptions

type InstallOptions struct {
	// Source is the user-supplied locator. classifySource picks the
	// SourceType based on shape and (for ambiguous bare names) a stat.
	Source string

	// Force overwrites an existing skill dir of the same slug. Without
	// it, an existing install is a hard error — matches the notes'
	// "Refuse overwrite unless --force".
	Force bool

	// DestRoot is the parent dir installs go into. Defaults to
	// UserSkillsDir() when empty. Tests inject a tempdir.
	DestRoot string

	// HTTPClient overrides the default client. Tests inject an
	// httptest server's client; production callers pass nil.
	HTTPClient *http.Client
}

InstallOptions configures one install attempt.

type InstallResult

type InstallResult struct {
	Skill      Skill
	Dir        string
	SourceType SourceType
	Lock       LockEntry
	Warnings   []string
}

InstallResult describes a completed install. Returned so the surface (CLI / TUI) can render "installed remote-ops (github) at /home/.../skills/remote-ops" without re-parsing the SKILL.md.

Warnings carries non-fatal post-install issues (lockfile save failure is the main one in Phase 2) so the caller can surface them to the user without failing the whole install — the bytes are on disk and the skill works regardless.

func Install

func Install(opts InstallOptions) (InstallResult, error)

Install resolves Source, validates its SKILL.md via ParseSkillFile, stages a copy in a tempdir, then atomic-renames into <DestRoot>/<slug>. Refuses to overwrite an existing slug unless Force is set.

type LoadResult

type LoadResult struct {
	Skills   []Skill
	Warnings []string
}

LoadResult is what LoadAll returns: a deduplicated, name-sorted slice of skills plus a slice of human-readable warnings the caller can surface to the user. Warnings are non-fatal — a single malformed skill should not block startup.

func LoadAll

func LoadAll(cwd string, reservedNames map[string]bool) (LoadResult, error)

LoadAll resolves skills from all three sources (built-in, user, project) and merges them with project > user > builtin precedence. reservedNames is the set of slash-command names a skill must not shadow — when a skill's name is in the set, the skill is dropped with a warning so the existing slash command keeps its meaning. Pass nil to skip the reserved-name guard (used by tests).

type LockEntry

type LockEntry struct {
	Name        string     `json:"name"`
	SourceType  SourceType `json:"source_type"`
	Source      string     `json:"source"`
	Hash        string     `json:"hash"`
	InstalledAt time.Time  `json:"installed_at"`
	Trust       string     `json:"trust"`
}

LockEntry is one installed skill's provenance row. Fields mirror the bullet list in the Phase 2 design notes verbatim.

type Lockfile

type Lockfile struct {
	Version int                  `json:"version"`
	Entries map[string]LockEntry `json:"entries"`
}

Lockfile is the top-level JSON document. Entries are keyed by skill name so lookups are O(1) and order on disk is stable.

func LoadLockfile

func LoadLockfile(destRoot string) (*Lockfile, error)

LoadLockfile reads <destRoot>/.lock.json. Missing file is not an error — first-time install will Save() and create it. A malformed file is an error so we never silently overwrite half-broken state.

func (*Lockfile) AllSorted

func (l *Lockfile) AllSorted() []LockEntry

AllSorted returns entries in name order. Used by `skills check` / `skills update` (no-name forms) so output is deterministic.

func (*Lockfile) Get

func (l *Lockfile) Get(name string) (LockEntry, bool)

Get looks up an entry by name. The bool mirrors map-lookup convention so callers can distinguish "missing" from "zero value."

func (*Lockfile) Remove

func (l *Lockfile) Remove(name string)

Remove drops an entry by name. No-op when the entry doesn't exist.

func (*Lockfile) Save

func (l *Lockfile) Save(destRoot string) error

Save writes the lockfile atomically (tempfile + rename). Caller is responsible for ensuring destRoot exists.

func (*Lockfile) Set

func (l *Lockfile) Set(entry LockEntry)

Set inserts or replaces an entry. Mutates the receiver in place; caller must Save() to persist.

type NewSkillOptions

type NewSkillOptions struct {
	// Slug is the skill's canonical name. Must match the parent
	// directory name (the parser enforces this), so we use the same
	// string for both.
	Slug string
	// DestRoot is the parent dir the new skill folder is created
	// under. Defaults to UserSkillsDir() when empty. Tests inject a
	// tempdir.
	DestRoot string
	// Force overwrites an existing directory of the same slug.
	// Mirrors the install --force semantics.
	Force bool
}

NewSkillOptions configures a scaffold call.

type NewSkillResult

type NewSkillResult struct {
	Dir       string
	SkillFile string
}

NewSkillResult describes a completed scaffold. Returned so the CLI can render "wrote ~/.yottacode/skills/<slug>/SKILL.md" without recomputing the path.

func NewSkill

func NewSkill(opts NewSkillOptions) (NewSkillResult, error)

NewSkill scaffolds a starter SKILL.md at <DestRoot>/<Slug>/. The body is a small template hinting at where to fill in the description, body, and resource dirs. Refuses to overwrite an existing dir unless Force is set.

type OfficialCatalogCache added in v0.4.0

type OfficialCatalogCache struct {
	Version   int       `json:"version"`
	UpdatedAt time.Time `json:"updated_at"`
	Skills    []Skill   `json:"skills"`
}

OfficialCatalogCache is the on-disk metadata cache for the Official tab.

type OfficialCatalogOptions added in v0.4.0

type OfficialCatalogOptions struct {
	HTTPClient *http.Client
}

OfficialCatalogOptions configures official catalog reads. HTTPClient is used only by RefreshOfficialCatalog; ListOfficialCatalog is intentionally offline.

type Scope

type Scope string

Scope distinguishes where a skill came from. Mirrors subagents/usercmd.

const (
	ScopeOfficial Scope = "official"
	ScopeBuiltin  Scope = "bundled"
	ScopeUser     Scope = "user"
	ScopeProject  Scope = "project"
)

type Skill

type Skill struct {
	Name          string            // required, [a-z0-9-]{1,64}, must match parent dir
	Description   string            // required, 1-1024 chars
	License       string            // optional
	Compatibility string            // optional, ≤500 chars, documentation-only
	Metadata      map[string]string // optional, free-form host-specific keys (e.g. slash, source-url)
	AllowedTools  []string          // optional, experimental; parsed and stored but not enforced in v1
	Body          string            // markdown body
	Dir           string            // absolute directory of the skill, or "" for embedded built-ins
	Source        Scope             // builtin | user | project
	SourcePath    string            // absolute path to SKILL.md, or "embed:builtins/<slug>/SKILL.md"
}

Skill is one parsed SKILL.md plus the directory it lives in. Body is the post-frontmatter markdown content; Dir is the absolute path of the skill's directory (used to resolve scripts/, references/, assets/ relative paths the body may reference).

Field shapes follow the canonical SKILL.md spec verbatim: name, description, license, compatibility, metadata, allowed-tools. We do not invent required fields beyond the spec.

func Find

func Find(skills []Skill, name string) *Skill

Find returns the named skill or nil. Case-sensitive: skill names are lowercase-only by the slug pattern, so casing is part of identity.

func ListOfficialCatalog added in v0.4.0

func ListOfficialCatalog(opts OfficialCatalogOptions) ([]Skill, error)

ListOfficialCatalog returns the cached official metadata catalog, falling back to bundled metadata when no cache exists. It never touches the network, so opening /skills -> Catalog is instant and immune to GitHub rate limits.

func LoadBuiltins

func LoadBuiltins() []Skill

LoadBuiltins parses every embedded SKILL.md under builtins/<slug>/ and returns the resulting Skill set. Source is always ScopeBuiltin so diagnostics can distinguish embedded vs disk-loaded definitions. Parse errors would be ship-blocking (the embedded files are part of the binary), so we panic — the test suite catches bad frontmatter before release.

SourcePath is set to "embed:builtins/<slug>/SKILL.md" so the path renders meaningfully in /help even though it does not exist on disk. Dir is left empty for built-ins; resources are read via the embedded FS through BuiltinResource, not the disk path.

func ParseSkillFile

func ParseSkillFile(data []byte, expectedName string) (Skill, error)

ParseSkillFile parses one SKILL.md into a Skill. expectedName is the parent directory name; the spec requires `name` to match the parent dir exactly, so we validate that here. Pass "" to skip the dir check (used by tests that hand in raw bytes).

Frontmatter format (YAML-ish, deliberately tolerant of hand-edits, matching the subagents/usercmd parsing style — no full YAML library dependency for what is functionally a flat map of scalars):

---
name: remote-ops
description: Connect to and operate on remote hosts over SSH.
license: MIT
compatibility: linux, darwin
metadata:
  slash: "true"
  source-url: https://github.com/example/remote-ops
allowed-tools: Bash(ssh:*) Bash(scp:*) Read
---
<markdown body>

func RefreshOfficialCatalog added in v0.4.0

func RefreshOfficialCatalog(opts OfficialCatalogOptions) ([]Skill, error)

RefreshOfficialCatalog fetches the public yottacode-skills catalog metadata from GitHub and writes ~/.yottacode/skills/catalog.json. It is an explicit user action; normal Catalog browsing remains offline.

func (Skill) SlashEnabled

func (s Skill) SlashEnabled() bool

SlashEnabled reports whether this skill should be exposed as a `/<name>` slash command. Default is enabled; opt-out by setting `metadata.slash: "false"` in the frontmatter.

type SourceType

type SourceType string

SourceType classifies an install source. Surfaced in InstallResult so the CLI/TUI can render "installed remote-ops from github" and so Phase 2's lockfile has the discriminator it needs.

const (
	SourceLocal    SourceType = "local"
	SourceURL      SourceType = "url"
	SourceGitHub   SourceType = "github"
	SourceOfficial SourceType = "official"
)

type UpdateOptions

type UpdateOptions struct {
	DestRoot   string
	Name       string
	Force      bool
	HTTPClient *http.Client
}

UpdateOptions tunes Update. DestRoot defaults to UserSkillsDir; HTTPClient is forwarded to the installer for the URL/GitHub paths. Name filters to one skill (no-arg `skills update` updates every tracked entry). Force overrides the user-modified skip.

type UpdateResult

type UpdateResult struct {
	Name    string
	Status  UpdateStatus
	OldHash string
	NewHash string
	Message string
}

UpdateResult is one line of the `skills update` report.

func Update

func Update(opts UpdateOptions) ([]UpdateResult, error)

Update re-runs Install for each lockfile entry, refreshing the installed bytes from the originally-recorded source. The dirty detection ensures we never clobber a hand-edit unless the user explicitly opts in via Force.

type UpdateStatus

type UpdateStatus string

UpdateStatus enumerates the outcomes Update can report for one skill. Mirrors CheckStatus where applicable; adds the installer-specific outcomes (updated, unchanged, skipped-dirty).

const (
	// UpdateUpdated — re-fetched and the new bytes differ from what
	// was on disk before. Lockfile entry refreshed.
	UpdateUpdated UpdateStatus = "updated"
	// UpdateUnchanged — re-fetched and the bytes are identical to
	// what was already installed. Lockfile InstalledAt is refreshed
	// but nothing else moves.
	UpdateUnchanged UpdateStatus = "unchanged"
	// UpdateSkippedDirty — on-disk copy differs from the recorded
	// hash and --force was not set. The user-modified bytes are
	// preserved; no fetch happens.
	UpdateSkippedDirty UpdateStatus = "skipped-user-modified"
	// UpdateSkippedNoLock — no lockfile entry, so Update can't know
	// where to refetch from. Surface the actionable hint
	// ("reinstall to record provenance").
	UpdateSkippedNoLock UpdateStatus = "skipped-no-lockfile"
	// UpdateError — the re-fetch failed (network, parse, etc.).
	// Existing bytes are preserved when possible.
	UpdateError UpdateStatus = "error"
)

type ValidateResult

type ValidateResult struct {
	Path  string
	Skill Skill
	Err   error
}

ValidateResult describes the outcome of a Validate call. Skill is populated on success (so callers can show the parsed name + description); Err carries the parser's error verbatim on failure so the user can see exactly which rule was violated.

func Validate

func Validate(path string) ValidateResult

Validate parses the SKILL.md at path against the same rules the runtime loader uses, returning the parsed Skill or the parser's error. path may be a directory (resolves to <path>/SKILL.md) or a SKILL.md file directly. The directory-name match is enforced only when path is a directory — pointing at a bare SKILL.md is for "is this file syntactically valid" lints in CI.

Jump to

Keyboard shortcuts

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