feat: client sync core — config, pull, inventory, manifest, OpenCode target
- admin-protected client config (flag > AGENT_LIB_CONFIG > platform default), token used for basic auth only, redacted from all errors and files - work-repo pull via go-git (bare cache clone + fetch, remote re-pointed on config change), clean failure keeps previous state - inventory: own items + external areas from lockfile; MCP inventoried but never deployed - manifest v1 records name/type/origin/revision/content-hash per item, deterministic bytes; folder hashes over sorted relpath+filehash lines so tree and disk hashes agree - shared deploy package (atomic temp+rename, exec-bit preserving) now backs both curator materialization and client deployment - sync validates and hashes everything before the first mutation; second run is a byte-level no-op
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
// Package deploy copies items from a discovery.Tree onto the filesystem,
|
||||
// preserving executable bits and writing atomically (temp + rename).
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/m3tam3re/agent-lib/internal/discovery"
|
||||
)
|
||||
|
||||
// Dir copies a folder item (e.g. one skill) from the tree into dst,
|
||||
// replacing whatever occupied dst before.
|
||||
func Dir(tree discovery.Tree, srcRel, dst string) error {
|
||||
lister, ok := tree.(interface {
|
||||
FilesUnder(prefix string) ([]string, error)
|
||||
})
|
||||
if !ok {
|
||||
return fmt.Errorf("tree does not support directory copy")
|
||||
}
|
||||
files, err := lister.FilesUnder(srcRel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(files) == 0 {
|
||||
return fmt.Errorf("no files under %s", srcRel)
|
||||
}
|
||||
parent := filepath.Dir(dst)
|
||||
if err := os.MkdirAll(parent, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
tmp, err := os.MkdirTemp(parent, ".agent-lib-tmp-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
prefix := strings.TrimSuffix(srcRel, "/") + "/"
|
||||
for _, f := range files {
|
||||
relInside := strings.TrimPrefix(f, prefix)
|
||||
target := filepath.Join(tmp, filepath.FromSlash(relInside))
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := File(tree, f, target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := os.RemoveAll(dst); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmp, dst)
|
||||
}
|
||||
|
||||
// File copies a single file from the tree to dst via temp file + rename.
|
||||
func File(tree discovery.Tree, srcRel, dst string) error {
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if mt, ok := tree.(interface {
|
||||
MaterializeFile(rel, dst string) error
|
||||
}); ok {
|
||||
tmp := dst + ".agent-lib-tmp"
|
||||
if err := mt.MaterializeFile(srcRel, tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmp, dst)
|
||||
}
|
||||
data, err := tree.ReadFile(srcRel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmp := dst + ".agent-lib-tmp"
|
||||
if err := os.WriteFile(tmp, data, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmp, dst)
|
||||
}
|
||||
Reference in New Issue
Block a user