241 lines
5.5 KiB
Go
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
|
|
}
|