203 lines
5.9 KiB
Go
203 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/yetone/magpie/internal/library"
|
|
)
|
|
|
|
const libraryUsage = `magpie library what the library gives each agent: instructions, MCP servers, skills
|
|
magpie library sync write it into the agents again (after one is installed, or edited by hand)
|
|
magpie library instructions print the shared instructions
|
|
magpie library instructions set <file|-> replace them with a file's text (- for stdin)
|
|
magpie library instructions agents <a,b…|none> the agents that get them
|
|
magpie library mcp add <name> <url | command args…> [agents=a,b…]
|
|
magpie library mcp agents <name> <a,b…|none>
|
|
magpie library mcp rm <name>
|
|
magpie library skill agents <name> <a,b…|none>
|
|
magpie library skill rm <name> (skills are installed from the app's Library page)
|
|
`
|
|
|
|
// libraryCmd is magpie library …: the instructions, MCP servers and skills
|
|
// magpie keeps once and writes into every agent.
|
|
func libraryCmd(args []string) error {
|
|
if len(args) < 2 {
|
|
return libraryStatus()
|
|
}
|
|
var (
|
|
res *library.Result
|
|
err error
|
|
)
|
|
switch sub, rest := args[1], args[2:]; sub {
|
|
case "sync":
|
|
res, err = library.Sync()
|
|
case "instructions":
|
|
if len(rest) == 0 {
|
|
v, err := library.ReadInstructions()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Print(v.Shared)
|
|
if v.Shared != "" && !strings.HasSuffix(v.Shared, "\n") {
|
|
fmt.Println()
|
|
}
|
|
return nil
|
|
}
|
|
switch {
|
|
case rest[0] == "set" && len(rest) == 2:
|
|
var b []byte
|
|
if rest[1] == "-" {
|
|
b, err = io.ReadAll(os.Stdin)
|
|
} else {
|
|
b, err = os.ReadFile(rest[1])
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
text := string(b)
|
|
res, err = library.SaveInstructions(library.InstructionsChange{Shared: &text})
|
|
case rest[0] == "agents" && len(rest) == 2:
|
|
res, err = library.SaveInstructions(library.InstructionsChange{Agents: agentList(rest[1])})
|
|
default:
|
|
return fmt.Errorf("usage:\n %s", libraryUsage)
|
|
}
|
|
case "mcp":
|
|
switch {
|
|
case len(rest) >= 3 && rest[0] == "add":
|
|
s := library.Server{Name: rest[1], Agents: []string{}}
|
|
var cmd []string
|
|
for _, a := range rest[2:] {
|
|
if v, ok := strings.CutPrefix(a, "agents="); ok {
|
|
s.Agents = agentList(v)
|
|
} else {
|
|
cmd = append(cmd, a)
|
|
}
|
|
}
|
|
switch {
|
|
case len(cmd) == 0:
|
|
return fmt.Errorf("a URL or a command is needed")
|
|
case strings.HasPrefix(cmd[0], "http://") || strings.HasPrefix(cmd[0], "https://"):
|
|
s.Transport, s.URL = "http", cmd[0]
|
|
if len(cmd) > 1 {
|
|
return fmt.Errorf("a server by URL takes nothing after it")
|
|
}
|
|
default:
|
|
s.Transport, s.Command, s.Args = "stdio", cmd[0], cmd[1:]
|
|
}
|
|
res, err = library.SaveServer("", s)
|
|
case len(rest) == 3 && rest[0] == "agents":
|
|
res, err = library.ServerAgents(rest[1], agentList(rest[2]))
|
|
case len(rest) == 2 && rest[0] == "rm":
|
|
res, err = library.RemoveServer(rest[1])
|
|
default:
|
|
return fmt.Errorf("usage:\n %s", libraryUsage)
|
|
}
|
|
case "skill", "skills":
|
|
switch {
|
|
case len(rest) == 3 && rest[0] == "agents":
|
|
res, err = library.SkillAgents(rest[1], agentList(rest[2]))
|
|
case len(rest) == 2 && rest[0] == "rm":
|
|
res, err = library.RemoveSkill(rest[1])
|
|
default:
|
|
return fmt.Errorf("usage:\n %s", libraryUsage)
|
|
}
|
|
case "help", "-h", "--help":
|
|
fmt.Print(" " + libraryUsage)
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("no library command %q\n %s", sub, libraryUsage)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
printLibraryResult(res)
|
|
return nil
|
|
}
|
|
|
|
// agentList is a comma list of agents; none (or nothing) is no agent.
|
|
func agentList(s string) []string {
|
|
out := []string{}
|
|
for _, a := range strings.Split(s, ",") {
|
|
if a = strings.TrimSpace(a); a != "" && a != "none" {
|
|
out = append(out, a)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func printLibraryResult(res *library.Result) {
|
|
if len(res.Changed) == 0 {
|
|
fmt.Println(green.Render("✓"), "every agent already has it")
|
|
} else {
|
|
fmt.Println(green.Render("✓"), "written into", strings.Join(res.Changed, ", "))
|
|
}
|
|
for _, p := range res.Problems {
|
|
fmt.Println(amber.Render("!"), p.Agent, muted.Render(p.What+":"), p.Error)
|
|
}
|
|
for _, m := range res.Missing {
|
|
fmt.Println(amber.Render("!"), "the library no longer has", m)
|
|
}
|
|
if res.Backup != "" {
|
|
fmt.Println(muted.Render(" what was there before is kept in " + res.Backup))
|
|
}
|
|
}
|
|
|
|
func libraryStatus() error {
|
|
v, err := library.Read(nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
on := func(ids []string) string {
|
|
if len(ids) == 0 {
|
|
return muted.Render("no agent")
|
|
}
|
|
return strings.Join(ids, ", ")
|
|
}
|
|
fmt.Println(bold.Render("Instructions"))
|
|
var to []string
|
|
for _, a := range v.Instructions.Agents {
|
|
if a.On {
|
|
to = append(to, a.Agent)
|
|
}
|
|
}
|
|
if strings.TrimSpace(v.Instructions.Shared) == "" {
|
|
fmt.Println(" ", muted.Render("none yet · magpie library instructions set <file>"))
|
|
} else {
|
|
fmt.Printf(" %d lines → %s\n", strings.Count(strings.TrimRight(v.Instructions.Shared, "\n"), "\n")+1, on(to))
|
|
}
|
|
for _, a := range v.Instructions.Agents {
|
|
if a.Edited {
|
|
fmt.Println(" ", amber.Render("!"), a.Agent, muted.Render("magpie's part was edited in "+a.Path))
|
|
}
|
|
}
|
|
fmt.Println(bold.Render("MCP servers"))
|
|
if len(v.Servers) == 0 {
|
|
fmt.Println(" ", muted.Render("none yet"))
|
|
}
|
|
for _, s := range v.Servers {
|
|
what := s.URL
|
|
if s.Command != "" {
|
|
what = strings.TrimSpace(s.Command + " " + strings.Join(s.Args, " "))
|
|
}
|
|
fmt.Printf(" %s %s → %s\n", bold.Render(s.Name), muted.Render(what), on(s.Agents))
|
|
for a, e := range s.Problems {
|
|
fmt.Println(" ", amber.Render("!"), a, muted.Render(e))
|
|
}
|
|
}
|
|
fmt.Println(bold.Render("Skills"))
|
|
if len(v.Skills) == 0 {
|
|
fmt.Println(" ", muted.Render("none yet"))
|
|
}
|
|
for _, s := range v.Skills {
|
|
fmt.Printf(" %s → %s\n", bold.Render(s.Name), on(s.Agents))
|
|
if s.Missing {
|
|
fmt.Println(" ", amber.Render("!"), muted.Render("its folder is gone"))
|
|
}
|
|
}
|
|
fmt.Println(muted.Render(" kept in " + v.Dir + " · magpie library help"))
|
|
return nil
|
|
}
|