skills

package
v0.95.4 Latest Latest
Warning

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

Go to latest
Published: Sep 4, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package skills installs WB's Agent Skills (embedded from ai/skills, see package ai) into a harness's own skills directory -- Claude Code's ~/.claude/skills, Cursor's ~/.cursor/skills, Codex's ~/.codex/skills -- so they are discoverable in every project, not only inside a checkout of sneat-dev/wb.

The gap this closes: WB ships agent-facing skills under ai/skills/ in its own repository, and a checkout-local plugin manifest auto-discovers them there -- but only for a session working inside that repository. A session orchestrating a different repository, with wb installed globally (Homebrew, go install, self-update), has never had those skills at all. `wb skills sync` is the fix: it copies every shipped skill into the harness's own skills directory once, so it is available everywhere wb is.

Index

Constants

View Source
const MarkerFileName = ".wb-skills-sync.json"

MarkerFileName is the sync record Sync writes next to the skills it installs -- e.g. ~/.claude/skills/.wb-skills-sync.json. Leading-dot so it never reads as a skill directory itself. It is the single source both the drift banner (main.go) and `wb skills sync`'s own idempotency check read, so the two never disagree about what was last installed.

Variables

This section is empty.

Functions

This section is empty.

Types

type Action

type Action string

Action classifies what Sync did (or, in a dry run, would do) with one skill.

const (
	// Added means the skill was not installed and now is.
	Added Action = "added"
	// Updated means an installed skill's content hash changed and was
	// replaced.
	Updated Action = "updated"
	// Unchanged means the installed skill already matches the embedded one;
	// nothing was written.
	Unchanged Action = "unchanged"
	// Removed means a skill this wb build no longer ships was previously
	// installed by wb and has been deleted.
	Removed Action = "removed"
	// Conflict means a directory already occupies a shipped skill's name but
	// was never recorded as wb-installed. Sync never overwrites or deletes
	// it; the caller must report it and leave it alone.
	Conflict Action = "conflict"
)

type Change

type Change struct {
	Name   string
	Action Action
}

Change is one skill's outcome from a Sync call.

type Marker

type Marker struct {
	SchemaVersion int               `json:"schema_version"`
	WBVersion     string            `json:"wb_version"`
	SyncedAt      time.Time         `json:"synced_at"`
	Skills        map[string]string `json:"skills"`
}

Marker records what `wb skills sync` last installed: which wb build ran it, when, and the exact content hash of every skill it wrote -- so a later run can tell added/updated/unchanged/removed apart without re-hashing installed files (which a user could have hand-edited) and so wb can print a drift warning by comparing WBVersion against the binary currently running, with no filesystem walk at all.

func ReadMarker

func ReadMarker(skillsDir string) (Marker, error)

ReadMarker reads the sync marker from skillsDir. A missing marker -- skills never synced on this machine -- is reported as os.ErrNotExist wrapped so callers can use errors.Is; every other read or parse failure is returned as-is.

type Options

type Options struct {
	// Source is the embedded skill tree, rooted so each skill's files sit at
	// "<name>/...". Ordinarily fs.Sub(ai.SkillsFS, "skills").
	Source fs.FS
	// Dir is the harness skills directory to install into, e.g.
	// ~/.claude/skills, ~/.cursor/skills, or ~/.codex/skills.
	Dir string
	// WBVersion is the running wb build's version, recorded in the marker.
	WBVersion string
	// DryRun computes and returns the Report without writing, removing, or
	// touching the marker.
	DryRun bool
}

Options configures one Sync call.

type Report

type Report struct {
	// Dir is the harness skills directory Sync targeted.
	Dir string
	// PriorWBVersion is the wb_version recorded in the marker before this
	// run, or "" when there was no marker (a first sync).
	PriorWBVersion string
	// WBVersion is the wb build that performed this sync, now recorded in
	// the marker (unless DryRun).
	WBVersion string
	// Changes covers every shipped skill plus every previously-installed
	// skill this build no longer ships, sorted by name.
	Changes []Change
	// DryRun reports whether this Report describes a plan rather than an
	// applied sync.
	DryRun bool
}

Report is the full outcome of one Sync call, in the shape `wb skills sync` prints and tests assert against.

func Sync

func Sync(opts Options) (Report, error)

Sync installs every skill in opts.Source into opts.Dir, and removes any skill this build no longer ships that a previous Sync installed there.

It is idempotent and always safe to re-run: a directory that already matches the embedded content is left untouched (Unchanged), and a directory Sync did not itself install -- because it predates any sync, or because its name collides with a shipped skill it never recorded owning -- is never overwritten or deleted (Conflict). Only names this exact function previously wrote, per the marker, are ever candidates for Removed.

func (Report) Changed

func (r Report) Changed() bool

Changed reports whether this sync (or plan) wrote, or would write, anything at all -- the idempotency signal: a second run with nothing new to ship reports Changed() == false.

func (Report) Names

func (r Report) Names(action Action) []string

Names returns the skill names classified with action, in Report order.

type Skill

type Skill struct {
	Name string
	Hash string
}

Skill is one shipped skill discovered in the embedded source: its name (the directory under ai/skills/, and the directory it is installed as under a harness skills directory) and a deterministic content hash of every file the skill directory contains.

func Discover

func Discover(source fs.FS) ([]Skill, error)

Discover lists every skill in source: an fs.FS rooted so that a skill's files sit at "<name>/SKILL.md", "<name>/references/...", and so on -- ordinarily fs.Sub(ai.SkillsFS, "skills"). A top-level entry only counts as a skill when it is a directory containing SKILL.md; commands.json and any other loose file at the same level is ignored.

Results are sorted by name so callers, and the marker file Sync writes, never depend on directory iteration order.

type Status

type Status struct {
	// Installed reports whether a marker exists at all, i.e. `wb skills
	// sync` has run on this machine before.
	Installed bool
	// SyncedWBVersion is the wb_version recorded in the marker. Empty when
	// !Installed.
	SyncedWBVersion string
}

Status is the cheap, marker-only read Drift and the SessionStart hook use: no embedded-skill discovery, no filesystem walk of the installed skills -- just the one small marker file, so it costs nothing to check on every wb invocation.

func ReadStatus

func ReadStatus(skillsDir string) (Status, error)

ReadStatus reads skillsDir's marker and reports it as a Status. A missing marker is not an error here -- it is the ordinary "never synced" case -- but any other read or parse failure is returned.

func (Status) Drifted

func (s Status) Drifted(currentWBVersion string) bool

Drifted reports whether currentWBVersion disagrees with the marker enough to warn about it: either skills were never synced, or they were synced by a different wb version than the one running now. An "unknown"/"(devel)" currentWBVersion -- an undetermined dev build -- never counts as drifted, so a plain `go build` of wb does not nag a developer about a comparison that means nothing.

Jump to

Keyboard shortcuts

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