bots-garden/mini-mepublic Fork 0
d72271127802973540c648bfb372176cdaaa8e4f
Commits
Clone
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

💾 Saved. d722711 · on d72271127802973540c648bfb372176cdaaa8e4f · k33g · 6h ago
skills.go · 140 lines · 4.2 KBGo Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Package skills discovers the markdown procedures in ./skills.
//
// A skill is a plain markdown file with a small front matter:
//
//	---
//	name: go-rename
//	description: rename a Go symbol everywhere it is used
//	---
//	# Rename a Go symbol
//	...
//
// The point of the package is to turn that directory into ONE STRING: the
// description of the `read_skill` tool. The model never lists the directory
// itself — it reads the catalogue in the tool description, the same way it
// reads the description of `bash`.
package skills

import (
	"os"
	"path/filepath"
	"sort"
	"strings"
)

// Skill is one markdown procedure on disk.
type Skill struct {
	Name        string // from the front matter, falling back to the file name
	Description string // the one-liner shown in the catalogue
	Path        string
}

// skillFile is the file name of a skill stored in its own directory — the
// Agent Skills convention (`<skillsDir>/<name>/SKILL.md`), the layout the
// shipped examples use.
const skillFile = "SKILL.md"

// defaultName is the name a skill gets when its front matter has none: the
// file name for a flat `<name>.md`, the directory name for `<name>/SKILL.md`.
func defaultName(path string) string {
	if filepath.Base(path) == skillFile {
		return filepath.Base(filepath.Dir(path))
	}
	return strings.TrimSuffix(filepath.Base(path), ".md")
}

// parseHeader reads the `name:` and `description:` lines of the front matter.
// It stops at the closing `---`: a `description:` in the body is not metadata.
func parseHeader(content, path string) Skill {
	s := Skill{Name: defaultName(path), Path: path}
	lines := strings.Split(content, "\n")
	if len(lines) == 0 || strings.TrimSpace(lines[0]) != "---" {
		return s
	}
	for _, line := range lines[1:] {
		if strings.TrimSpace(line) == "---" {
			break
		}
		key, value, found := strings.Cut(line, ":")
		if !found {
			continue
		}
		value = strings.TrimSpace(value)
		switch strings.TrimSpace(key) {
		case "name":
			if value != "" {
				s.Name = value
			}
		case "description":
			s.Description = value
		}
	}
	return s
}

// List returns the skills of dir, sorted by name. Two layouts are accepted,
// and may be mixed: a flat `<dir>/<name>.md`, and one directory per skill,
// `<dir>/<name>/SKILL.md`. A missing directory is not an error: it just means
// this agent has no skills.
func List(dir string) []Skill {
	flat, err := filepath.Glob(filepath.Join(dir, "*.md"))
	if err != nil {
		return nil
	}
	nested, err := filepath.Glob(filepath.Join(dir, "*", skillFile))
	if err != nil {
		return nil
	}
	var out []Skill
	for _, p := range append(flat, nested...) {
		content, err := os.ReadFile(p)
		if err != nil {
			continue
		}
		out = append(out, parseHeader(string(content), p))
	}
	sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
	return out
}

// Read returns the full markdown of one skill, whichever layout it uses: the
// flat `<name>.md` is tried first, then `<name>/SKILL.md`.
func Read(dir, name string) (string, error) {
	// Keep the name a plain file name: no "../" escaping out of the directory.
	name = filepath.Base(name)
	content, err := os.ReadFile(filepath.Join(dir, name+".md"))
	if err != nil {
		if nested, nestedErr := os.ReadFile(filepath.Join(dir, name, skillFile)); nestedErr == nil {
			return string(nested), nil
		}
	}
	return string(content), err
}

// Catalogue renders the list as the tool description the model will read.
// This IS the prompt engineering: what the model knows about the available
// skills is exactly these lines.
func Catalogue(list []Skill) string {
	var b strings.Builder
	b.WriteString("Load a skill: the step-by-step procedure to follow for this kind of task. " +
		"Call it BEFORE doing the work, with the name of the matching skill, and then follow " +
		"what it says. Available skills:\n")
	for _, s := range list {
		b.WriteString("  " + s.Name)
		if s.Description != "" {
			b.WriteString(" — " + s.Description)
		}
		b.WriteString("\n")
	}
	return b.String()
}

// Names lists the skill names, for an error message that helps the model
// recover from a typo.
func Names(list []Skill) []string {
	out := make([]string, 0, len(list))
	for _, s := range list {
		out = append(out, s.Name)
	}
	return out
}