Documentation
¶
Overview ¶
Package skillembed ships agent skills inside a Go binary.
It is the reverse of `gh skill install`. A tool carries its own skills in an embed.FS, and writes them into the directory the user's agent reads from. The agent directories and the flag set are taken from `gh skill install`, so a user who knows that command already knows this one.
Skills live under skills/<name>/SKILL.md, which is the layout defined by the Agent Skills specification (https://agentskills.io/specification).
//go:embed skills var skillsFS embed.FS
A bare //go:embed drops every file whose name begins with a dot or an underscore, and says nothing about it. Write all:skills when a skill holds one. That form keeps everything, .DS_Store included, so SkillsFromFS refuses a skill carrying a file an operating system left behind and names it.
var skills = skillembed.NewInstaller(
skillembed.MustSkillsFromFS(skillsFS, "skills"),
skillembed.WithToolName("mytool"),
skillembed.WithVersion("v0.1.0"),
)
Installer.Intercept gives a tool the skill subcommand in one line. It works for a go/analysis driver too, where no hook exists once the driver starts. Installer.Run is for a tool that parses its own arguments. The skillcobra, skillurfavev3 and skillurfavev2 modules wire the same commands into those frameworks.
Index ¶
- Constants
- Variables
- func RenderCLIResults(results []InstallResult, dryRun bool) string
- func RenderCLIStatus(statuses []InstallStatus) string
- type Action
- type Agent
- type AgentSelector
- type ForceRequiredError
- type InstallOptions
- type InstallResult
- type InstallStatus
- type InstallTarget
- type Installer
- func (in *Installer) AgentChoices() string
- func (in *Installer) CommandName() string
- func (in *Installer) DefaultScope() Scope
- func (in *Installer) Install(ctx context.Context, o InstallOptions) ([]InstallResult, error)
- func (in *Installer) Intercept()
- func (in *Installer) Run(ctx context.Context, args []string) error
- func (in *Installer) Status(ctx context.Context, o InstallOptions) ([]InstallStatus, error)
- func (in *Installer) Targets(o InstallOptions) ([]InstallTarget, error)
- func (in *Installer) ToolName() string
- func (in *Installer) Uninstall(ctx context.Context, o InstallOptions) ([]InstallResult, error)
- func (in *Installer) Usage() string
- func (in *Installer) UsageHint() string
- type InstallerOption
- func WithAgents(agents ...Agent) InstallerOption
- func WithCommandName(name string) InstallerOption
- func WithDefaultAgents(selectors ...AgentSelector) InstallerOption
- func WithDefaultScope(s Scope) InstallerOption
- func WithErrorOutput(w io.Writer) InstallerOption
- func WithExecutable(fn func(name string, data []byte) bool) InstallerOption
- func WithMetadata(on bool) InstallerOption
- func WithOutput(w io.Writer) InstallerOption
- func WithProjectRoot(dir string) InstallerOption
- func WithToolName(name string) InstallerOption
- func WithVersion(v string) InstallerOption
- type Scope
- type Skill
- type SkillSet
- type State
Examples ¶
Constants ¶
const ( MetaKeyEmbeddedBy = manifest.KeyEmbeddedBy MetaKeyEmbeddedVersion = manifest.KeyEmbeddedVersion MetaKeyEmbeddedAt = manifest.KeyEmbeddedAt MetaKeyEmbeddedDigest = manifest.KeyEmbeddedDigest )
Frontmatter keys written into an installed SKILL.md. They let a later run tell an outdated copy from one that was edited by hand.
const SkillFile = manifest.FileName
SkillFile is the manifest every skill directory must contain, as defined by the Agent Skills specification (https://agentskills.io/specification).
Variables ¶
var ( AgentGitHubCopilot = Agent{ Name: "github-copilot", Title: "GitHub Copilot", ProjectDir: sharedAgentProjectDir, UserDir: agentHomeDir(".copilot", "skills"), } AgentClaudeCode = Agent{ Name: "claude-code", Title: "Claude Code", ProjectDir: ".claude/skills", UserDir: agentEnvOrHomeDir("CLAUDE_CONFIG_DIR", []string{"skills"}, ".claude", "skills"), } AgentCursor = Agent{ Name: "cursor", Title: "Cursor", ProjectDir: sharedAgentProjectDir, UserDir: agentHomeDir(".cursor", "skills"), } AgentCodex = Agent{ Name: "codex", Title: "Codex", ProjectDir: sharedAgentProjectDir, UserDir: agentHomeDir(".codex", "skills"), } AgentGemini = Agent{ Name: "gemini", Title: "Gemini CLI", ProjectDir: sharedAgentProjectDir, UserDir: agentHomeDir(".gemini", "skills"), } AgentAntigravity = Agent{ Name: "antigravity", Title: "Antigravity", ProjectDir: sharedAgentProjectDir, UserDir: agentHomeDir(".gemini", "antigravity", "skills"), } )
Built-in agents. The directories match `gh skill install`.
var ErrHelp = flag.ErrHelp
ErrHelp is returned by Run when help was requested. It is not a failure.
var ErrNeedsForce = errors.New("skillembed: destination needs force")
ErrNeedsForce reports that a destination held something this tool did not write, or something edited after it did. Install leaves those skills alone and reports the rest, so a caller branches on this rather than on the text:
results, err := skills.Install(ctx, o)
printed(results)
if errors.Is(err, skillembed.ErrNeedsForce) {
// tell the user to re-run with --force
}
var ErrNoAgentSelected = errors.New("skillembed: no agent selected")
ErrNoAgentSelected reports that the values resolved to nothing at all.
var ErrProjectIsHome = projectroot.ErrIsHome
ErrProjectIsHome reports that project scope resolved to the home directory.
The user scope directories live there. A project installation written into them would put one project's skills in front of every other project, and --scope user already writes there on purpose.
var ErrUnknownAgent = errors.New("skillembed: unknown agent")
ErrUnknownAgent reports an --agent value that names no agent this tool offers. A front end can map it to a usage exit code.
var ErrUnknownScope = errors.New("skillembed: unknown scope")
ErrUnknownScope reports a --scope value that is neither project nor user.
var ErrUnknownSkill = errors.New("skillembed: unknown skill")
ErrUnknownSkill reports a name that matches no embedded skill.
Functions ¶
func RenderCLIResults ¶
func RenderCLIResults(results []InstallResult, dryRun bool) string
RenderCLIResults lists what install or uninstall did at each destination. The adapters use it so that every front end reports the same way.
func RenderCLIStatus ¶
func RenderCLIStatus(statuses []InstallStatus) string
RenderCLIStatus is everything `list` prints: each skill once with its description, then a row per destination.
It is the whole of the subcommand's output, not a part of it, so that every front end that calls it says the same thing. The skills come from the statuses rather than from the set, so a run narrowed by name describes only what it was asked about.
Types ¶
type Action ¶
type Action string
Action is what install or uninstall did at one destination.
const ( // ActionInstalled means the skill was written where nothing was. ActionInstalled Action = "installed" // ActionUpdated means an existing installation was replaced. ActionUpdated Action = "updated" // ActionRemoved means the skill directory was deleted. ActionRemoved Action = "removed" // ActionSkipped means nothing was done. Result.Reason says why. ActionSkipped Action = "skipped" )
type Agent ¶
type Agent struct {
// Name is the flag value, e.g. "claude-code".
Name string
// Title is the human readable name, e.g. "Claude Code".
Title string
// ProjectDir is the skills directory relative to the project root.
ProjectDir string
// UserDir returns the absolute skills directory for user scope.
UserDir func() (string, error)
}
Agent is a coding agent that reads skills from a known directory.
The built-in agents mirror the directories used by `gh skill install`.
func DefaultAgents ¶
func DefaultAgents() []Agent
DefaultAgents lists every built-in agent, in the order `gh skill install` documents them.
type AgentSelector ¶
type AgentSelector string
AgentSelector names one agent, or a group of them, as --agent accepts it.
The value space is every agent name the installer offers, plus the two group words below. An Agent value cannot carry those words, which is why this is its own type rather than []Agent.
const ( // AgentSelectorAll is every agent the installer offers, present or not. AgentSelectorAll AgentSelector = "all" // AgentSelectorDetected is the agents whose directory is already there. // It falls back to AgentSelectorAll when it finds none. AgentSelectorDetected AgentSelector = "detected" )
func AgentSelectorFor ¶
func AgentSelectorFor(a Agent) AgentSelector
AgentSelectorFor names one agent.
skills.Install(ctx, skillembed.InstallOptions{
Agents: []skillembed.AgentSelector{
skillembed.AgentSelectorFor(skillembed.AgentClaudeCode),
},
})
type ForceRequiredError ¶
type ForceRequiredError struct {
// Blocked is the state of each destination that was not written.
Blocked []InstallStatus
}
ForceRequiredError names every skill Install left alone.
func (*ForceRequiredError) Error ¶
func (e *ForceRequiredError) Error() string
func (*ForceRequiredError) Unwrap ¶
func (*ForceRequiredError) Unwrap() error
Unwrap lets a caller match with errors.Is(err, ErrNeedsForce).
type InstallOptions ¶
type InstallOptions struct {
// Agents select the destinations. Empty means the installer default.
Agents []AgentSelector
// Scope is ScopeProject or ScopeUser. Empty means the installer default.
Scope Scope
// Dir installs into this directory, overriding Agents and Scope.
Dir string
// Force overwrites skills that were edited, or that something else installed.
Force bool
// DryRun reports what would happen without touching the file system.
DryRun bool
// Names selects skills by name. Empty means every embedded skill.
Names []string
}
InstallOptions are the inputs shared by install, uninstall and list.
type InstallResult ¶
type InstallResult struct {
Skill Skill
// Target is the destination this row is about.
Target InstallTarget
Path string
// Before is the state found at Path.
Before State
Action Action
// Reason explains a skipped action.
Reason string
}
InstallResult is the outcome for one skill at one target.
type InstallStatus ¶
type InstallStatus struct {
Skill Skill
// Target is the destination this row is about.
Target InstallTarget
// Path is the skill's own directory inside InstallTarget.Dir.
Path string
State State
// InstalledBy and InstalledVersion come from the installed frontmatter.
InstalledBy string
InstalledVersion string
}
InstallStatus is the state of one skill at one destination.
type InstallTarget ¶
type InstallTarget struct {
// Dir is the absolute skills directory.
Dir string
// Agents read from Dir. It is empty when InstallOptions.Dir was used.
Agents []Agent
// Root is the project directory Dir was resolved against. It is empty for
// user scope, and when InstallOptions.Dir named the destination outright.
Root string
}
InstallTarget is one destination directory and the agents that read from it.
func (InstallTarget) Label ¶
func (t InstallTarget) Label() string
Label renders the target for human readable output.
type Installer ¶
type Installer struct {
// contains filtered or unexported fields
}
Installer installs embedded skills into agent directories.
func NewInstaller ¶
func NewInstaller(set *SkillSet, opts ...InstallerOption) *Installer
NewInstaller creates an Installer for a set of embedded skills.
func (*Installer) AgentChoices ¶
AgentChoices renders the --agent help string, such as {a|b|c}. An adapter that writes its own flag help uses it to name the same agents.
func (*Installer) CommandName ¶
CommandName returns the subcommand name.
func (*Installer) DefaultScope ¶
DefaultScope returns the scope used when none is given.
func (*Installer) Install ¶
func (in *Installer) Install(ctx context.Context, o InstallOptions) ([]InstallResult, error)
Install writes the selected skills into the resolved targets.
A destination this tool did not write, or one edited after it did, is left alone unless InstallOptions.Force is set. Those skills come back as ActionSkipped with a Reason, exactly as Uninstall reports them, and the error wraps ErrNeedsForce. One blocked destination does not stop the others from being written.
The results are meaningful even when the error is not nil. They describe everything that happened before it.
Example ¶
package main
import (
"context"
"embed"
"fmt"
"log"
"os"
skillembed "github.com/mpyw/go-skill-embed"
)
// These skills hold no file whose name begins with a dot or an underscore, so
// the bare form is enough. Write all:testdata/skills when one does, since the
// bare form drops them without a word.
//
//go:embed testdata/skills
var exampleSkills embed.FS
func main() {
ctx := context.Background()
skills := skillembed.NewInstaller(
skillembed.MustSkillsFromFS(exampleSkills, "testdata/skills"),
skillembed.WithToolName("mytool"),
skillembed.WithVersion("v0.1.0"),
)
dir, err := os.MkdirTemp("", "skills")
if err != nil {
log.Fatal(err)
}
defer func() { _ = os.RemoveAll(dir) }()
results, err := skills.Install(ctx, skillembed.InstallOptions{
Dir: dir,
Names: []string{"demo-skill"},
})
if err != nil {
log.Fatal(err)
}
for _, r := range results {
fmt.Println(r.Action, r.Skill.Name)
}
// Installing again writes nothing, because the digest recorded in the
// installed SKILL.md still matches.
results, err = skills.Install(ctx, skillembed.InstallOptions{
Dir: dir,
Names: []string{"demo-skill"},
})
if err != nil {
log.Fatal(err)
}
for _, r := range results {
fmt.Println(r.Action, r.Skill.Name, "-", r.Reason)
}
}
Output: installed demo-skill skipped demo-skill - already up to date
func (*Installer) Intercept ¶
func (in *Installer) Intercept()
Intercept runs the skill command when it is the first argument, and exits.
Call it as the first statement of main, before flag.Parse or any driver that reads the command line itself:
func main() {
skills.Intercept()
flag.Parse()
// the rest of your tool
}
The guard is the first argument and nothing else. `mytool skill install` reaches it. `mytool -v skill install` does not. A `go vet -vettool=` invocation passes -flags or a config file path, so it is never affected.
singlechecker, unitchecker and multichecker read every non-flag argument as a package pattern, and they parse the command line themselves. No later point exists at which a subcommand could still be recognised, so Intercept has to run before them.
Example ¶
Intercept goes before flag.Parse, because the skill command is not a flag and has to be the first argument.
package main
import (
"embed"
"flag"
"fmt"
"os"
skillembed "github.com/mpyw/go-skill-embed"
)
// These skills hold no file whose name begins with a dot or an underscore, so
// the bare form is enough. Write all:testdata/skills when one does, since the
// bare form drops them without a word.
//
//go:embed testdata/skills
var exampleSkills embed.FS
func main() {
skills := skillembed.NewInstaller(
skillembed.MustSkillsFromFS(exampleSkills, "testdata/skills"),
skillembed.WithToolName("mytool"),
)
verbose := flag.Bool("v", false, "print what is happening")
// Your own help says nothing about the skill command unless you say it.
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\n%s\n", skills.UsageHint())
}
// `mytool skill install` is handled here and never returns.
// `mytool -v ./...` falls through to the tool itself.
skills.Intercept()
flag.Parse()
fmt.Println(*verbose, flag.Args())
}
Output:
Example (AnalysisDriver) ¶
A go/analysis driver reads every non-flag argument as a package pattern, so there is no point after it starts at which a subcommand is still visible.
package main
import (
"embed"
skillembed "github.com/mpyw/go-skill-embed"
)
// These skills hold no file whose name begins with a dot or an underscore, so
// the bare form is enough. Write all:testdata/skills when one does, since the
// bare form drops them without a word.
//
//go:embed testdata/skills
var exampleSkills embed.FS
func main() {
skills := skillembed.NewInstaller(
skillembed.MustSkillsFromFS(exampleSkills, "testdata/skills"),
skillembed.WithToolName("mylint"),
)
// This is the whole of main, in order.
skills.Intercept()
// singlechecker.Main(mylint.Analyzer)
}
Output:
func (*Installer) Run ¶
Run executes the skill command. args are the arguments after the command name, so `mytool skill install --scope user` passes []string{"install", "--scope", "user"}.
It never calls os.Exit, so a driver can keep control. Run is what the cobra and urfave/cli adapters call underneath, and what Intercept wraps.
Example ¶
Run is for a tool that already parses its own arguments. It reports errors instead of exiting, so the caller keeps control, and asking for help is not a failure.
package main
import (
"context"
"embed"
"errors"
"log"
"os"
skillembed "github.com/mpyw/go-skill-embed"
)
// These skills hold no file whose name begins with a dot or an underscore, so
// the bare form is enough. Write all:testdata/skills when one does, since the
// bare form drops them without a word.
//
//go:embed testdata/skills
var exampleSkills embed.FS
func main() {
ctx := context.Background()
skills := skillembed.NewInstaller(
skillembed.MustSkillsFromFS(exampleSkills, "testdata/skills"),
skillembed.WithToolName("mytool"),
skillembed.WithOutput(os.Stdout),
)
if err := skills.Run(ctx, nil); err != nil && !errors.Is(err, skillembed.ErrHelp) {
log.Fatal(err)
}
}
Output: Manage the agent skills embedded in mytool. Usage: mytool skill install [flags] [skill...] mytool skill uninstall [flags] [skill...] mytool skill list [flags] [skill...] Flags: -agent value Target agent: {github-copilot|claude-code|cursor|codex|gemini|antigravity}, or all, or detected (repeatable) (default "detected") -dir string Install to a custom directory (overrides -agent and -scope) -dry-run Report what would happen without writing -f Overwrite existing skills (shorthand) -force Overwrite existing skills -scope value Installation scope: {project|user} (default project) Embedded skills: bare-skill demo-skill A skill used by this module's tests. It is not meant to be installed.
func (*Installer) Status ¶
func (in *Installer) Status(ctx context.Context, o InstallOptions) ([]InstallStatus, error)
Status reports what is installed where, without changing anything.
Example ¶
package main
import (
"context"
"embed"
"fmt"
"log"
"os"
skillembed "github.com/mpyw/go-skill-embed"
)
// These skills hold no file whose name begins with a dot or an underscore, so
// the bare form is enough. Write all:testdata/skills when one does, since the
// bare form drops them without a word.
//
//go:embed testdata/skills
var exampleSkills embed.FS
func main() {
ctx := context.Background()
skills := skillembed.NewInstaller(
skillembed.MustSkillsFromFS(exampleSkills, "testdata/skills"),
skillembed.WithToolName("mytool"),
)
dir, err := os.MkdirTemp("", "skills")
if err != nil {
log.Fatal(err)
}
defer func() { _ = os.RemoveAll(dir) }()
report := func() {
statuses, err := skills.Status(ctx, skillembed.InstallOptions{Dir: dir})
if err != nil {
log.Fatal(err)
}
for _, st := range statuses {
fmt.Println(st.Skill.Name, st.State)
}
}
report()
if _, err := skills.Install(ctx, skillembed.InstallOptions{Dir: dir}); err != nil {
log.Fatal(err)
}
report()
}
Output: bare-skill missing demo-skill missing bare-skill up-to-date demo-skill up-to-date
func (*Installer) Targets ¶
func (in *Installer) Targets(o InstallOptions) ([]InstallTarget, error)
Targets resolves the destination directories for o.
At project scope every agent but Claude Code shares .agents/skills, so those are merged into one target and a skill is never written there twice.
func (*Installer) Uninstall ¶
func (in *Installer) Uninstall(ctx context.Context, o InstallOptions) ([]InstallResult, error)
Uninstall removes the selected skills from the resolved targets. Skills this tool did not install are left alone unless InstallOptions.Force is set.
The results are meaningful even when the error is not nil.
func (*Installer) Usage ¶
Usage is the help text for the skill command. A tool that writes its own help can print it, so that the two agree.
func (*Installer) UsageHint ¶
UsageHint is one line naming the skill command, for a tool whose own help would otherwise never mention it. flag.Usage and an analyzer's Doc are the two places it belongs.
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\n%s\n", skills.UsageHint())
}
type InstallerOption ¶
type InstallerOption func(*Installer)
InstallerOption configures an Installer.
func WithAgents ¶
func WithAgents(agents ...Agent) InstallerOption
WithAgents restricts the agents the tool offers. It defaults to every built-in agent.
func WithCommandName ¶
func WithCommandName(name string) InstallerOption
WithCommandName sets the subcommand name used by Run and Intercept. It defaults to "skill".
func WithDefaultAgents ¶
func WithDefaultAgents(selectors ...AgentSelector) InstallerOption
WithDefaultAgents sets the agents used when --agent is not given. It defaults to "detected".
"detected" keeps the agents whose directory is already there, and falls back to "all" when it finds none. In a fresh repository that is two directories and reaches everything. In a home directory it is the agents in use, rather than six directories of which most are litter. Pass "github-copilot" for the default `gh skill install` uses.
func WithDefaultScope ¶
func WithDefaultScope(s Scope) InstallerOption
WithDefaultScope sets the scope used when --scope is not given. It defaults to project, matching `gh skill install`.
func WithErrorOutput ¶
func WithErrorOutput(w io.Writer) InstallerOption
WithErrorOutput sets where Run writes diagnostics: the message for a bad flag or an unknown subcommand, and the usage that goes with it. It defaults to os.Stderr.
The two are separate for the sake of redirection. `mytool skill list > skills.txt` puts the list in the file and the complaint on the terminal.
func WithExecutable ¶
func WithExecutable(fn func(name string, data []byte) bool) InstallerOption
WithExecutable decides which files are written with the executable bit. embed.FS does not carry file modes, so the default marks any file starting with a #! shebang.
func WithMetadata ¶
func WithMetadata(on bool) InstallerOption
WithMetadata controls whether installed skills carry x-embedded-* frontmatter. Without it, install cannot tell an outdated copy from an edited one and every existing directory reads as foreign. It is on by default.
func WithOutput ¶
func WithOutput(w io.Writer) InstallerOption
WithOutput sets where Run writes what was asked for: the report, and the help text when help was requested. It defaults to os.Stdout.
func WithProjectRoot ¶
func WithProjectRoot(dir string) InstallerOption
WithProjectRoot sets the directory project scope resolves against.
Without it the root is searched for: the walk starts at the working directory and stops at the repository root, and the first directory already holding an agent directory wins. Outside a repository the working directory is the only candidate. The home directory is refused, since the user scope directories live there.
func WithToolName ¶
func WithToolName(name string) InstallerOption
WithToolName sets the name recorded in installed skills and shown in help. It defaults to the running binary's name.
func WithVersion ¶
func WithVersion(v string) InstallerOption
WithVersion sets the version recorded in installed skills.
type Skill ¶
type Skill struct {
// Name is the directory name the skill is installed under. It comes from
// the `name` frontmatter field, falling back to the source directory name.
Name string
// Description is the `description` frontmatter field, if any.
Description string
// Dir is the skill's path inside the source file system.
Dir string
// contains filtered or unexported fields
}
Skill is one embedded skill directory.
type SkillSet ¶
type SkillSet struct {
// contains filtered or unexported fields
}
SkillSet is a collection of embedded skills, in name order.
func MustSkillsFromFS ¶
MustSkillsFromFS is SkillsFromFS for a package level variable. It panics, because a broken embed is a build-time mistake and not a runtime condition.
Example ¶
package main
import (
"embed"
"fmt"
skillembed "github.com/mpyw/go-skill-embed"
)
// These skills hold no file whose name begins with a dot or an underscore, so
// the bare form is enough. Write all:testdata/skills when one does, since the
// bare form drops them without a word.
//
//go:embed testdata/skills
var exampleSkills embed.FS
func main() {
set := skillembed.MustSkillsFromFS(exampleSkills, "testdata/skills")
for _, sk := range set.Skills() {
fmt.Println(sk.Name)
}
}
Output: bare-skill demo-skill
func SkillsFromFS ¶
SkillsFromFS discovers skills under root using the `<root>/*/SKILL.md` convention. If root itself holds a SKILL.md it is read as a single skill.
//go:embed skills var skillsFS embed.FS
A bare //go:embed leaves out every file whose name begins with a dot or an underscore, and says nothing about it. Write all:skills when a skill holds one. A skill carrying a file an operating system left behind, such as .DS_Store, is refused either way.
type State ¶
type State string
State describes what is already present at a destination.
const ( // StateMissing means nothing is installed there yet. StateMissing State = "missing" // StateUpToDate means the installed copy matches the embedded skill. StateUpToDate State = "up-to-date" // StateOutdated means this tool installed it and it is not what the binary // would write now. The binary carries a newer copy, or a file lost the // executable bit it was installed with. StateOutdated State = "outdated" // StateModified means this tool installed it and the files were edited // afterwards. StateModified State = "modified" // StateForeign means something else owns a skill of that name there. StateForeign State = "foreign" )
func (State) NeedsForce ¶
NeedsForce reports whether overwriting this state would destroy work that this tool did not create.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
manifest
Package manifest reads and rewrites the frontmatter of a SKILL.md.
|
Package manifest reads and rewrites the frontmatter of a SKILL.md. |
|
projectroot
Package projectroot finds the directory a project scope install writes into.
|
Package projectroot finds the directory a project scope install writes into. |
|
skillfs
Package skillfs hashes and materialises a skill directory.
|
Package skillfs hashes and materialises a skill directory. |
|
textfmt
Package textfmt shapes strings for a terminal listing.
|
Package textfmt shapes strings for a terminal listing. |
|
skillcobra
module
|
|
|
skillurfavev2
module
|
|
|
skillurfavev3
module
|