feat: 自托管 AI Gateway 运维管理 SKILL
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
package skillbundle
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
Name = "ai-gateway-ops-management"
|
||||
DisplayName = "AI Gateway 运维管理"
|
||||
)
|
||||
|
||||
const bundleRoot = "content/skills/" + Name
|
||||
|
||||
var semverPattern = regexp.MustCompile(`^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$`)
|
||||
|
||||
//go:embed content/skills/ai-gateway-ops-management
|
||||
var bundleFS embed.FS
|
||||
|
||||
type Metadata struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Modules []string `json:"modules"`
|
||||
}
|
||||
|
||||
func LoadMetadata() (Metadata, error) {
|
||||
raw, err := bundleFS.ReadFile(bundleRoot + "/skill.json")
|
||||
if err != nil {
|
||||
return Metadata{}, fmt.Errorf("read bundled skill metadata: %w", err)
|
||||
}
|
||||
var metadata Metadata
|
||||
if err := json.Unmarshal(raw, &metadata); err != nil {
|
||||
return Metadata{}, fmt.Errorf("parse bundled skill metadata: %w", err)
|
||||
}
|
||||
if metadata.Name != Name {
|
||||
return Metadata{}, fmt.Errorf("bundled skill metadata name must be %q", Name)
|
||||
}
|
||||
if !semverPattern.MatchString(metadata.Version) {
|
||||
return Metadata{}, fmt.Errorf("bundled skill metadata version %q is not semver", metadata.Version)
|
||||
}
|
||||
if len(metadata.Modules) == 0 {
|
||||
return Metadata{}, fmt.Errorf("bundled skill metadata modules must not be empty")
|
||||
}
|
||||
seen := make(map[string]struct{}, len(metadata.Modules))
|
||||
for _, module := range metadata.Modules {
|
||||
module = strings.TrimSpace(module)
|
||||
if module == "" {
|
||||
return Metadata{}, fmt.Errorf("bundled skill metadata module must not be empty")
|
||||
}
|
||||
if _, exists := seen[module]; exists {
|
||||
return Metadata{}, fmt.Errorf("bundled skill metadata module %q is duplicated", module)
|
||||
}
|
||||
seen[module] = struct{}{}
|
||||
}
|
||||
if _, err := bundleFS.ReadFile(bundleRoot + "/SKILL.md"); err != nil {
|
||||
return Metadata{}, fmt.Errorf("read bundled SKILL.md: %w", err)
|
||||
}
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func FileName(metadata Metadata) string {
|
||||
return fmt.Sprintf("%s-v%s.zip", metadata.Name, metadata.Version)
|
||||
}
|
||||
|
||||
func BuildArchive() ([]byte, error) {
|
||||
if _, err := LoadMetadata(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files := make([]string, 0)
|
||||
if err := fs.WalkDir(bundleFS, bundleRoot, func(filePath string, entry fs.DirEntry, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
if entry.IsDir() {
|
||||
return nil
|
||||
}
|
||||
relativePath := strings.TrimPrefix(filePath, bundleRoot+"/")
|
||||
if relativePath == "" || path.IsAbs(relativePath) || path.Clean(relativePath) != relativePath || strings.HasPrefix(relativePath, "../") {
|
||||
return fmt.Errorf("unsafe bundled skill path %q", relativePath)
|
||||
}
|
||||
files = append(files, relativePath)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("walk bundled skill: %w", err)
|
||||
}
|
||||
sort.Strings(files)
|
||||
|
||||
var output bytes.Buffer
|
||||
archive := zip.NewWriter(&output)
|
||||
fixedTime := time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
for _, relativePath := range files {
|
||||
raw, err := bundleFS.ReadFile(bundleRoot + "/" + relativePath)
|
||||
if err != nil {
|
||||
_ = archive.Close()
|
||||
return nil, fmt.Errorf("read bundled skill file %q: %w", relativePath, err)
|
||||
}
|
||||
header := &zip.FileHeader{Name: relativePath, Method: zip.Deflate}
|
||||
header.SetModTime(fixedTime)
|
||||
writer, err := archive.CreateHeader(header)
|
||||
if err != nil {
|
||||
_ = archive.Close()
|
||||
return nil, fmt.Errorf("create bundled skill archive entry %q: %w", relativePath, err)
|
||||
}
|
||||
if _, err := writer.Write(raw); err != nil {
|
||||
_ = archive.Close()
|
||||
return nil, fmt.Errorf("write bundled skill archive entry %q: %w", relativePath, err)
|
||||
}
|
||||
}
|
||||
if err := archive.Close(); err != nil {
|
||||
return nil, fmt.Errorf("close bundled skill archive: %w", err)
|
||||
}
|
||||
return output.Bytes(), nil
|
||||
}
|
||||
Reference in New Issue
Block a user