Files
sproutclaw-web/backend/internal/services/commands.go
2026-06-13 20:16:09 +08:00

92 lines
2.4 KiB
Go

package services
import (
"os"
"path/filepath"
"strings"
"sproutclaw-web/internal/models"
)
// builtinCommands are the slash commands the WebUI handles natively.
var builtinCommands = []models.SlashCommand{
{Name: "new", Description: "开启新会话", Source: "builtin"},
{Name: "compact", Description: "压缩会话历史", Source: "builtin"},
{Name: "reload", Description: "重载配置", Source: "builtin"},
{Name: "clone", Description: "克隆当前会话", Source: "builtin"},
{Name: "name", Description: "重命名当前会话", Source: "builtin"},
{Name: "model", Description: "切换模型", Source: "builtin"},
{Name: "session", Description: "显示会话统计", Source: "builtin"},
{Name: "export", Description: "导出为 HTML", Source: "builtin"},
{Name: "copy", Description: "复制最后一条助手消息", Source: "builtin"},
{Name: "webui", Description: "WebUI 控制", Source: "builtin"},
}
// ListSlashCommands aggregates all slash commands visible in the WebUI.
func ListSlashCommands(agentDir string) []models.SlashCommand {
cmds := make([]models.SlashCommand, len(builtinCommands))
copy(cmds, builtinCommands)
// scan skills dir for SKILL.md files
skillsDir := filepath.Join(agentDir, "skills")
cmds = append(cmds, scanSkillCommands(skillsDir)...)
return cmds
}
func scanSkillCommands(skillsDir string) []models.SlashCommand {
entries, err := os.ReadDir(skillsDir)
if err != nil {
return nil
}
var cmds []models.SlashCommand
for _, e := range entries {
if !e.IsDir() {
continue
}
skillMd := filepath.Join(skillsDir, e.Name(), "SKILL.md")
name, desc := parseSkillMD(skillMd)
if name == "" {
name = e.Name()
}
cmds = append(cmds, models.SlashCommand{
Name: name,
Description: desc,
Source: "skill",
})
}
return cmds
}
func parseSkillMD(path string) (name, desc string) {
data, err := os.ReadFile(path)
if err != nil {
return "", ""
}
content := string(data)
// parse frontmatter between --- delimiters
if !strings.HasPrefix(content, "---") {
return "", ""
}
parts := strings.SplitN(content, "---", 3)
if len(parts) < 3 {
return "", ""
}
fm := parts[1]
for _, line := range strings.Split(fm, "\n") {
kv := strings.SplitN(line, ":", 2)
if len(kv) != 2 {
continue
}
k := strings.TrimSpace(kv[0])
v := strings.TrimSpace(kv[1])
switch k {
case "name":
name = v
case "description":
desc = v
}
}
return name, desc
}