117 lines
3.1 KiB
Go
117 lines
3.1 KiB
Go
package command
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// CustomCommand represents a user-defined command loaded from a markdown file.
|
|
type CustomCommand struct {
|
|
Name string
|
|
Description string
|
|
Template string // prompt template with {{input}} placeholder
|
|
}
|
|
|
|
// LoadCustomCommands reads .md files from the commands directory and returns
|
|
// parsed custom commands. Each file should have YAML-like frontmatter:
|
|
//
|
|
// ---
|
|
// name: review
|
|
// description: Code review prompt
|
|
// ---
|
|
// Review this code: {{input}}
|
|
func LoadCustomCommands(dir string) []CustomCommand {
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
var cmds []CustomCommand
|
|
for _, entry := range entries {
|
|
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".md") {
|
|
continue
|
|
}
|
|
data, err := os.ReadFile(filepath.Join(dir, entry.Name()))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if cmd, ok := parseCustomCommand(string(data)); ok {
|
|
cmds = append(cmds, cmd)
|
|
}
|
|
}
|
|
return cmds
|
|
}
|
|
|
|
// parseCustomCommand parses a markdown file with YAML frontmatter.
|
|
func parseCustomCommand(content string) (CustomCommand, bool) {
|
|
content = strings.TrimSpace(content)
|
|
if !strings.HasPrefix(content, "---") {
|
|
return CustomCommand{}, false
|
|
}
|
|
|
|
// Find end of frontmatter.
|
|
rest := content[3:]
|
|
idx := strings.Index(rest, "---")
|
|
if idx < 0 {
|
|
return CustomCommand{}, false
|
|
}
|
|
|
|
frontmatter := rest[:idx]
|
|
body := strings.TrimSpace(rest[idx+3:])
|
|
|
|
cmd := CustomCommand{Template: body}
|
|
|
|
// Parse simple key: value pairs from frontmatter.
|
|
for _, line := range strings.Split(frontmatter, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
parts := strings.SplitN(line, ":", 2)
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
key := strings.TrimSpace(parts[0])
|
|
val := strings.TrimSpace(parts[1])
|
|
switch key {
|
|
case "name":
|
|
cmd.Name = val
|
|
case "description":
|
|
cmd.Description = val
|
|
}
|
|
}
|
|
|
|
if cmd.Name == "" || cmd.Template == "" {
|
|
return CustomCommand{}, false
|
|
}
|
|
|
|
return cmd, true
|
|
}
|
|
|
|
// RegisterCustomCommands loads and registers custom commands from the given directory.
|
|
func RegisterCustomCommands(r *Registry, dir string) {
|
|
cmds := LoadCustomCommands(dir)
|
|
for _, cc := range cmds {
|
|
// Capture for closure.
|
|
tmpl := cc.Template
|
|
desc := cc.Description
|
|
if desc == "" {
|
|
desc = "Custom command"
|
|
}
|
|
|
|
r.Register(&Command{
|
|
Name: cc.Name,
|
|
Description: desc,
|
|
Handler: func(_ *Context, args []string) Result {
|
|
input := strings.Join(args, " ")
|
|
prompt := strings.ReplaceAll(tmpl, "{{input}}", input)
|
|
return Result{
|
|
Action: ActionSendPrompt,
|
|
Data: prompt,
|
|
}
|
|
},
|
|
})
|
|
}
|
|
}
|