zot/internal/skills/builtin.go
patriceckhart 2cffe048c9 feat(skills): built-in extension-author skill, hidden from /skills
The model now ships with a `write-zot-extension` skill compiled
into the binary. When the user asks for help authoring a zot
extension (slash command, LLM tool, audit hook, permission gate)
the model sees the skill in its system-prompt manifest, calls
the `skill` tool to load the body on demand, and walks the user
through the right answer with the wire format, manifest shape,
SDK examples (Go + TS + Python), and dev workflow already in
context. No need for the user to be in the zot repo or to ask
the model to read docs/extensions.md first.

Built-in skills:
  - shipped via //go:embed at internal/skills/builtin/
  - merged into Discover()'s output AFTER user skills, so a
    user-installed skill with the same name shadows the built-in
    (drop your own SKILL.md at $ZOT_HOME/skills/write-zot-extension/
    to customise)
  - marked Builtin: true on the Skill struct
  - hidden from user-facing surfaces: VisibleSkills() filters them
    so /skills only shows skills the user actually installed or
    shipped in their project

The model side stays unchanged: system-prompt manifest still lists
built-ins (so the model knows they exist), the `skill` tool still
loads them on demand. Only the picker is filtered.

Verified live:
  prompt: "List the names of the skills you have available"
  -> code-review, test-fix, write-zot-extension

  prompt: "I want to write a zot extension that adds a slash
           command /pwd which inserts the current directory path
           into the editor. What language should I use, and what
           files do I need to create?"
  -> [tool_call] skill({"name":"write-zot-extension"})
  -> body returned
  -> the model produces a complete extension with the right
     manifest, the right hello/register/ready frames, action:
     insert correctly chosen, and a remark about cwd capture.

The picker filter has its own unit test
(TestVisibleSkillsHidesBuiltins) and the existing Discover test
was updated to expect the built-in count without hardcoding it.
2026-04-19 15:55:25 +02:00

51 lines
1.3 KiB
Go

package skills
import (
"embed"
"io/fs"
"path"
"strings"
)
// builtinFS holds the SKILL.md files zot ships with the binary.
// They appear in the catalogue as ordinary skills — same on-demand
// load via the `skill` tool, same /skills picker — but never need
// to be installed by the user. A user-installed skill with the same
// name shadows the built-in one (Discover's first-match-wins).
//
//go:embed all:builtin
var builtinFS embed.FS
// loadBuiltins returns every SKILL.md compiled into the binary.
// Errors per file are silently dropped: built-ins are part of the
// release; if one is malformed it's a release bug we want to surface
// in tests, not panic in front of the user.
func loadBuiltins() []*Skill {
entries, err := fs.ReadDir(builtinFS, "builtin")
if err != nil {
return nil
}
var out []*Skill
for _, e := range entries {
if !e.IsDir() {
continue
}
raw, err := fs.ReadFile(builtinFS, path.Join("builtin", e.Name(), "SKILL.md"))
if err != nil {
continue
}
front, body := splitFrontmatter(string(raw))
s := &Skill{
Path: "builtin:" + e.Name(),
Source: "built-in",
Body: strings.TrimSpace(body),
Builtin: true,
}
parseFrontmatter(front, s)
if s.Name == "" {
s.Name = e.Name()
}
out = append(out, s)
}
return out
}