sdaduanbilei 9e279a0627 fix
2026-03-05 17:34:49 +08:00

241 lines
5.5 KiB
Go

package hub
import (
"archive/zip"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
type Team struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Author string `yaml:"author"`
RepoURL string `yaml:"repo_url"`
Agents []string `yaml:"agents"`
Skills []string `yaml:"skills"`
InstalledAt string `yaml:"installed_at"`
}
// Install clones a git repo (any URL) and installs the team.
func Install(repoRef, agentsDir, skillsDir, teamsDir string) (*Team, error) {
url := repoRef
if !strings.HasPrefix(repoRef, "http") && !strings.HasPrefix(repoRef, "git@") {
url = "https://github.com/" + repoRef
}
// Extract repo name for team name
repoName := extractRepoName(url)
tmp, err := os.MkdirTemp("", "agent-team-hub-*")
if err != nil {
return nil, err
}
defer os.RemoveAll(tmp)
cmd := exec.Command("git", "clone", "--depth=1", url, tmp)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("git clone: %w", err)
}
return installFromDir(tmp, repoName, url, agentsDir, skillsDir, teamsDir)
}
// InstallZIP extracts a ZIP file and installs the team.
func InstallZIP(zipPath, agentsDir, skillsDir, teamsDir string) (*Team, error) {
// Extract team name from zip filename
teamName := strings.TrimSuffix(filepath.Base(zipPath), ".zip")
teamName = strings.TrimSuffix(teamName, "-main")
teamName = strings.TrimSuffix(teamName, "-master")
tmp, err := os.MkdirTemp("", "agent-team-hub-*")
if err != nil {
return nil, err
}
defer os.RemoveAll(tmp)
// Extract ZIP
if err := extractZIP(zipPath, tmp); err != nil {
return nil, fmt.Errorf("extract zip: %w", err)
}
// Find the root directory (might be wrapped in a subfolder)
entries, _ := os.ReadDir(tmp)
if len(entries) == 1 && entries[0].IsDir() {
tmp = filepath.Join(tmp, entries[0].Name())
}
return installFromDir(tmp, teamName, "", agentsDir, skillsDir, teamsDir)
}
func installFromDir(tmp, teamName, repoURL, agentsDir, skillsDir, teamsDir string) (*Team, error) {
// Copy agents
installedAgents := []string{}
srcAgentsDir := filepath.Join(tmp, "agents")
if _, err := os.Stat(srcAgentsDir); err == nil {
// Copy to team-specific directory
dstAgentsDir := filepath.Join(agentsDir, teamName)
if err := copyDir(srcAgentsDir, dstAgentsDir); err == nil {
entries, _ := os.ReadDir(srcAgentsDir)
for _, e := range entries {
if e.IsDir() {
installedAgents = append(installedAgents, e.Name())
}
}
}
}
// Copy skills
installedSkills := []string{}
srcSkillsDir := filepath.Join(tmp, "skills")
if _, err := os.Stat(srcSkillsDir); err == nil {
dstSkillsDir := filepath.Join(skillsDir, teamName)
if err := copyDir(srcSkillsDir, dstSkillsDir); err == nil {
entries, _ := os.ReadDir(srcSkillsDir)
for _, e := range entries {
if e.IsDir() {
installedSkills = append(installedSkills, e.Name())
}
}
}
}
// Create knowledge dir
os.MkdirAll(filepath.Join(agentsDir, teamName, "knowledge"), 0755)
// Create team record
team := &Team{
Name: teamName,
Description: "团队描述",
RepoURL: repoURL,
Agents: installedAgents,
Skills: installedSkills,
InstalledAt: time.Now().Format("2006-01-02"),
}
// Save team record
teamDir := filepath.Join(teamsDir, teamName)
os.MkdirAll(teamDir, 0755)
team.save(filepath.Join(teamDir, "team.yaml"))
return team, nil
}
func extractZIP(zipPath, dest string) error {
r, err := zip.OpenReader(zipPath)
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
fpath := filepath.Join(dest, f.Name)
if f.FileInfo().IsDir() {
os.MkdirAll(fpath, 0755)
continue
}
if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil {
return err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
rc, err := f.Open()
if err != nil {
outFile.Close()
return err
}
buf := make([]byte, 4096)
for {
n, err := rc.Read(buf)
if n > 0 {
if _, writeErr := outFile.Write(buf[:n]); writeErr != nil {
rc.Close()
outFile.Close()
return writeErr
}
}
if err != nil {
break
}
}
rc.Close()
outFile.Close()
}
return nil
}
func extractRepoName(url string) string {
parts := strings.Split(url, "/")
name := parts[len(parts)-1]
name = strings.TrimSuffix(name, ".git")
return name
}
func (t *Team) save(path string) error {
// Simple YAML save
content := fmt.Sprintf(`name: %s
description: %s
author: %s
repo_url: %s
agents:
%s
skills:
%s
installed_at: %s
`, t.Name, t.Description, t.Author, t.RepoURL, arrayToYAML(t.Agents), arrayToYAML(t.Skills), t.InstalledAt)
return os.WriteFile(path, []byte(content), 0644)
}
func arrayToYAML(arr []string) string {
if len(arr) == 0 {
return " []"
}
result := ""
for _, s := range arr {
result += fmt.Sprintf(" - %s\n", s)
}
return result
}
func copyDir(src, dst string) error {
if _, err := os.Stat(src); os.IsNotExist(err) {
return nil // optional dir, skip
}
os.MkdirAll(dst, 0755)
entries, err := os.ReadDir(src)
if err != nil {
return err
}
for _, e := range entries {
srcPath := filepath.Join(src, e.Name())
dstPath := filepath.Join(dst, e.Name())
if e.IsDir() {
if err := copyDir(srcPath, dstPath); err != nil {
return err
}
} else {
data, err := os.ReadFile(srcPath)
if err != nil {
return err
}
os.MkdirAll(filepath.Dir(dstPath), 0755)
if err := os.WriteFile(dstPath, data, 0644); err != nil {
return err
}
}
}
return nil
}