scorpio de773586c7 feat: implement full agent-team platform
Go backend:
- LLM client with DeepSeek/Kimi/Ollama/OpenAI support (OpenAI-compat)
- Agent loader: AGENT.md frontmatter, SOUL.md, memory read/write
- Skill system following agentskills.io standard
- Room orchestration: master assign→execute→review loop with streaming
- Hub: GitHub repo clone and team package install
- Echo HTTP server with WebSocket and full REST API

React frontend:
- Discord-style 3-panel layout with Tailwind v4
- Zustand store with WebSocket streaming message handling
- Chat view: streaming messages, role styles, right panel, drawer buttons
- Agent MD editor with Monaco Editor (AGENT.md + SOUL.md)
- Market page for GitHub team install/publish

Docs:
- plan.md with full progress tracking and next steps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 21:57:46 +08:00

96 lines
2.1 KiB
Go

package skill
import (
"bytes"
"fmt"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
type Meta struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Path string `yaml:"-"`
}
type Skill struct {
Meta
Body string // full SKILL.md body (instructions)
}
// Discover scans skillsDir and returns metadata for all valid skills.
func Discover(skillsDir string) ([]Meta, error) {
entries, err := os.ReadDir(skillsDir)
if err != nil {
return nil, err
}
var metas []Meta
for _, e := range entries {
if !e.IsDir() {
continue
}
path := filepath.Join(skillsDir, e.Name(), "SKILL.md")
data, err := os.ReadFile(path)
if err != nil {
continue
}
meta, err := parseMeta(data)
if err != nil {
continue
}
meta.Path = filepath.Join(skillsDir, e.Name())
metas = append(metas, meta)
}
return metas, nil
}
// Load returns a fully loaded skill including body.
func Load(skillDir string) (*Skill, error) {
data, err := os.ReadFile(filepath.Join(skillDir, "SKILL.md"))
if err != nil {
return nil, err
}
meta, err := parseMeta(data)
if err != nil {
return nil, err
}
meta.Path = skillDir
body := extractBody(data)
return &Skill{Meta: meta, Body: body}, nil
}
// ToXML generates <available_skills> XML for agent system prompts.
func ToXML(metas []Meta) string {
var sb strings.Builder
sb.WriteString("<available_skills>\n")
for _, m := range metas {
fmt.Fprintf(&sb, " <skill>\n <name>%s</name>\n <description>%s</description>\n <location>%s/SKILL.md</location>\n </skill>\n",
m.Name, m.Description, m.Path)
}
sb.WriteString("</available_skills>")
return sb.String()
}
func parseMeta(data []byte) (Meta, error) {
var meta Meta
if !bytes.HasPrefix(data, []byte("---")) {
return meta, fmt.Errorf("missing frontmatter")
}
parts := bytes.SplitN(data, []byte("---"), 3)
if len(parts) < 3 {
return meta, fmt.Errorf("invalid frontmatter")
}
return meta, yaml.Unmarshal(parts[1], &meta)
}
func extractBody(data []byte) string {
parts := bytes.SplitN(data, []byte("---"), 3)
if len(parts) < 3 {
return string(data)
}
return strings.TrimSpace(string(parts[2]))
}