Files
agent-lib/internal/client/config.go
T
m3ta-chiron 49dbcf469e 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
2026-08-23 10:31:00 +02:00

112 lines
2.9 KiB
Go

// Package client implements the employee side: config loading, work-repo
// pull, inventory, manifest and deployment into the OpenCode target.
package client
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
)
// ClientConfig is the admin-protected fleet configuration for sync.
type ClientConfig struct {
RepoURL string `json:"repo_url"`
Ref string `json:"ref,omitempty"`
Token string `json:"token,omitempty"`
TokenUser string `json:"token_user,omitempty"`
}
// ConfigEnv names the override environment variables.
const (
ConfigEnv = "AGENT_LIB_CONFIG"
StateDirEnv = "AGENT_LIB_STATE_DIR"
defaultConfig = `{
"repo_url": "",
"ref": "",
"token": "",
"token_user": ""
}`
)
// DefaultConfigPath returns the admin-protected config location per platform:
// ProgramData on Windows, /etc on Linux/macOS.
func DefaultConfigPath() string {
if runtime.GOOS == "windows" {
if pd := os.Getenv("ProgramData"); pd != "" {
return filepath.Join(pd, "agent-lib", "config.json")
}
return `C:\ProgramData\agent-lib\config.json`
}
return "/etc/agent-lib/config.json"
}
// LoadClientConfig reads the config from flagPath, the override env, or the
// platform default — in that order.
func LoadClientConfig(flagPath string) (*ClientConfig, error) {
path := flagPath
if path == "" {
path = os.Getenv(ConfigEnv)
}
if path == "" {
path = DefaultConfigPath()
}
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading client config %s: %w", path, err)
}
var cfg ClientConfig
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("client config %s is not valid JSON: %w", path, err)
}
if cfg.RepoURL == "" {
return nil, fmt.Errorf("client config %s: repo_url is required", path)
}
return &cfg, nil
}
// StateDir returns the client state directory: the override env, then the
// platform default (LocalAppData on Windows, XDG state elsewhere).
func StateDir() (string, error) {
if d := os.Getenv(StateDirEnv); d != "" {
return d, nil
}
if runtime.GOOS == "windows" {
base := os.Getenv("LocalAppData")
if base == "" {
return "", fmt.Errorf("LocalAppData is not set")
}
return filepath.Join(base, "agent-lib"), nil
}
if xdg := os.Getenv("XDG_STATE_HOME"); xdg != "" {
return filepath.Join(xdg, "agent-lib"), nil
}
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, ".local", "state", "agent-lib"), nil
}
// StatePaths bundles the well-known files inside the state dir.
type StatePaths struct {
Root string
CacheDir string
Manifest string
LogFile string
}
func ResolveStatePaths() (StatePaths, error) {
root, err := StateDir()
if err != nil {
return StatePaths{}, err
}
return StatePaths{
Root: root,
CacheDir: filepath.Join(root, "work-repo"),
Manifest: filepath.Join(root, "manifest.json"),
LogFile: filepath.Join(root, "sync.log"),
}, nil
}