// 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 }