feat(client): config search chain, --repo zero-config override, actionable missing-config error

sync/status now resolve the work repository from a fallback chain:
--repo flag > --config flag > AGENT_LIB_CONFIG env > user-level config
(XDG / AppData) > admin platform default. --repo works without any
config file; tokens fall back to AGENT_LIB_GIT_TOKEN/AGENT_LIB_GIT_USER
without ever overriding a configured token. A completely empty chain
yields an error listing every searched location, a minimal config
example and the --repo shortcut.

Closes beads: agent-lib-9o8
This commit is contained in:
2026-08-23 15:08:27 +02:00
parent 1b2fcb6fbb
commit 7070d8cc25
7 changed files with 627 additions and 31 deletions
+36 -2
View File
@@ -10,9 +10,13 @@ import (
"github.com/m3tam3re/agent-lib/internal/target"
)
// SyncOptions parameterize a client sync run.
// SyncOptions parameterize a client sync run. RepoURL (with optional Ref)
// is the inline --repo override: it works without any config file and beats
// a repo_url discovered in the config chain.
type SyncOptions struct {
ConfigPath string
RepoURL string
Ref string
}
// prepared is everything sync and status share: the pulled repository, the
@@ -28,7 +32,10 @@ type prepared struct {
// prepare pulls, inventories, hashes and plans — the complete read-only
// front half of a client run.
func prepare(opts SyncOptions) (*prepared, error) {
cfg, err := LoadClientConfig(opts.ConfigPath)
if opts.RepoURL == "" && opts.Ref != "" {
return nil, fmt.Errorf("--ref requires --repo (it only overrides the repository to pull)")
}
cfg, err := resolveRunConfig(opts)
if err != nil {
return nil, err
}
@@ -82,6 +89,33 @@ func prepare(opts SyncOptions) (*prepared, error) {
return &prepared{paths: paths, pull: pull, manifest: manifest, items: items, plan: plan}, nil
}
// resolveRunConfig applies the --repo inline override to the config chain:
// with --repo, any discovered config only contributes auth (and ref, unless
// --ref is also given); without --repo, a config with a repo_url is required.
func resolveRunConfig(opts SyncOptions) (*ClientConfig, error) {
if opts.RepoURL == "" {
cfg, err := LoadClientConfig(opts.ConfigPath)
if err != nil {
return nil, err
}
cfg.FillTokenAuth()
return cfg, nil
}
cfg, err := LoadOptionalConfig(opts.ConfigPath)
if err != nil {
return nil, err
}
if cfg == nil {
cfg = &ClientConfig{}
}
cfg.RepoURL = opts.RepoURL
if opts.Ref != "" {
cfg.Ref = opts.Ref
}
cfg.FillTokenAuth()
return cfg, nil
}
// Sync pulls the work repository, plans the sync as a pure function over
// (inventory, manifest, disk facts) and executes the plan. Validation and
// hashing happen before the first mutation; unreachable repositories fail