# agent-lib One Go binary, two roles: **curator** and **client**. - **Curator side** — `agent-lib vendor …` pulls external git repositories into a plain work repository under `external///`, pinned by revision, with per-source selection and collision-safe naming. Publication is nothing more than `git commit && git push`. - **Client side** — `agent-lib sync` pulls the work repository and deploys its content into the OpenCode runtime paths. Local modifications win over company updates, self-created items are never touched, and Windows toast notifications tell employees when their edits blocked an update. No Nix, no Python, no git subprocess at runtime — a single static binary for windows/amd64, linux/amd64, linux/arm64, darwin/amd64 and darwin/arm64. ## Installation Download the binary for your platform from the [GitHub releases](https://github.com/m3tam3re/agent-lib/releases), verify the checksum, and put it on your `PATH`: ```sh curl -fsSLO https://github.com/m3tam3re/agent-lib/releases/latest/download/agent-lib_linux_amd64.tar.gz tar xzf agent-lib_linux_amd64.tar.gz sha256sum -c checksums.txt --ignore-missing install -m 0755 agent-lib /usr/local/bin/ agent-lib version ``` Fleet-managed machines receive the binary via a pinned rollout — see [docs/fleet-handoff.md](docs/fleet-handoff.md). There is no self-update. From source: `go build .` (version is baked in via ldflags; `make build` uses the git description). ## Curator guide The work repository layout: ``` skills//SKILL.md own skills (folder artifacts) commands/.md own OpenCode commands agents/.md own OpenCode agents mcp/.yaml own MCP fragments (never deployed by the binary) external/// vendored content, mirrors the same four types agent-lib.lock.json lockfile v2 — pins every source ``` ### Vendoring a source ```sh agent-lib vendor add superpowers https://github.com/obra/superpowers/tree/main/skills ``` Pasted GitHub/GitLab web-tree URLs are normalized automatically. A URL pointing directly at a type directory (e.g. `…/tree/main/skills`) selects that directory as the skills root. `--ref ` pins a ref; the resolved revision is pinned in the lockfile. Discovery understands the four standard directories (`skills/`, `commands/`, `agents/`, `mcp/`); skills are folders containing `SKILL.md` — found at **any depth**, so category-nested layouts (`skills/engineering/tdd/SKILL.md`) work too, with the folder's base name as the skill id — commands and agents are flat `.md` files, MCP fragments are `.yaml`. Everything is read-only scanned — upstream code is never executed. ### Selection rules - Default: vendor **everything** (`mode: all`). - `--include a,b,c` switches the source to **include mode**: only those ids arrive, and new upstream items do **not** arrive automatically. - Hard errors, enforced by `vendor add`, `vendor update`, `vendor select` and `validate`: - `all` combined with an include list → error - an exclude list without `all` → error (not supported in v2 selections) - an include entry that no longer exists upstream → error naming the item (`vendor select` changes selections later — see [Changing your selection later](#changing-your-selection-later) below.) ### Changing your selection later `vendor select` manages the selection after the initial add — without ever advancing the pinned revision (that is `vendor update`'s job): ```sh agent-lib vendor select superpowers --add tdd,brainstorm # include mode: grow agent-lib vendor select superpowers --remove review,debug # include mode: shrink agent-lib vendor select superpowers --remove review # all mode: move to exclude agent-lib vendor select superpowers --mode all # reset: take everything again ``` - **include mode**: `--add` appends ids (validated against the pinned upstream — typos abort with the list of available ids), `--remove` drops ids and deletes their folders from `external/`. - **all mode**: `--remove` moves ids onto the exclude list (lockfile v2 supports exclude in all mode) and deletes their folders; `--add` is a hard error — everything is already selected. - `--mode all` clears include/exclude and re-materializes the full pinned upstream, reporting what appeared. Every select re-runs the collision checks and honors the lockfile rename map — you can hand-add a rename for a not-yet-selected item, then `--add` it under its deployed name. Failed selects mutate nothing. ### Collisions and renames Every item deploys into a flat per-type namespace. If a vendored id collides with one of your own items (or with another source's item), the operation aborts with a hard error naming both parties — before anything is written. Resolve it with an explicit, reviewable rename: ```sh agent-lib vendor add superpowers "$URL" --rename review=superpowers-review ``` The on-disk folder always carries exactly the deployed name. Renaming onto another collision is an error. ### The maintenance loop ```sh agent-lib vendor diff superpowers # read-only preview: new/changed/deleted vs selection agent-lib vendor update superpowers # fetch latest of the pinned ref, rewrite, report agent-lib vendor update --all # every source, reported per source agent-lib vendor remove superpowers # delete external area + lockfile entry ``` In `all` mode new upstream items arrive automatically (and appear in the update report); in include mode they deliberately do not — `vendor diff` surfaces them as `available upstream, not selected`. Every update re-runs the collision checks and honors the rename map. ### Auditing ```sh agent-lib validate # offline: rules + tree/lockfile consistency agent-lib vendor list # sources with pin, mode, item counts agent-lib vendor inspect # full inventory with metadata + warnings ``` All three accept `--json`. The lockfile is deterministic JSON (sorted keys, fixed indentation) — identical reruns produce identical bytes, so git diffs stay clean. ## Client guide Employees run two commands: ```sh agent-lib sync # pull the work repository and deploy agent-lib status # report local state; exit 1 when updates are blocked ``` Both accept `--repo ` / `--ref ` for config-free personal use — see [Configuration](#configuration) below. Deployment mapping (OpenCode target): | Type | Destination | |---|---| | skills | `~/.agents/skills//` (whole artifact folders) | | commands | `~/.config/opencode/commands/.md` | | agents | `~/.config/opencode/agents/.md` | | mcp | **never deployed** — stays under fleet-controller merge | ### Sync semantics (three-state) Sync plans before it mutates. For every item the planner compares the repository, the local manifest and the on-disk content: - new item → **deploy** - managed & unmodified, upstream changed → **update** - managed & locally modified → **skip + warn** — your version stays - on disk but never deployed by us → **leave + warn** — self-created items are safe by construction - upstream deleted & unmodified → **remove** - upstream deleted & modified → **skip + warn** — your version stays Sync is idempotent (a second run is a byte-level no-op) and fails cleanly: an unreachable repository never leaves a half-deployed machine. ### Configuration The repository to pull comes from the first source that provides one: 1. `--repo ` flag (works with **no config file at all**) 2. `--config ` flag 3. `AGENT_LIB_CONFIG` env 4. user-level config (personal machines) 5. admin-protected platform default (fleet rollout) | Source | Linux / macOS | Windows | |---|---|---| | user-level config | `$XDG_CONFIG_HOME/agent-lib/config.json` (default `~/.config/agent-lib/config.json`) | `%AppData%\agent-lib\config.json` | | admin default | `/etc/agent-lib/config.json` | `%ProgramData%\agent-lib\config.json` | `--repo` overrides a `repo_url` from any discovered config; `--ref` narrows it to a branch or tag. When no config exists anywhere, the error lists every searched location, a minimal working example, and the `--repo` shortcut. ```json { "repo_url": "https://git.example.com/company/agent-content.git", "ref": "main", "token": "", "token_user": "oauth2" } ``` The token is used for basic auth only. It never appears in the lockfile, the manifest, logs or error messages. On `--repo` runs (or when the discovered config carries no token) `AGENT_LIB_GIT_TOKEN` / `AGENT_LIB_GIT_USER` supply credentials instead — env never overrides a configured token. **Personal quickstart (no fleet, no config):** ```sh agent-lib sync --repo https://github.com/you/your-agent-content.git ``` The work repository must be a committed git repository (sync pulls it read-only via go-git; local uncommitted state is ignored). ### Notifications On Windows, a toast fires when (and only when) a sync kept local modifications or discovered unmanaged items — never for routine syncs. The same warnings are appended to the sync log (`~/.local/state/agent-lib/sync.log` on Linux, `%LocalAppData%\agent-lib` on Windows) and surfaced by `status`. Linux/macOS notification is a no-op. `status --json` is machine-readable: deployed revision, repository revision, pending changes, skipped and unrecognized items, and a `problematic_drift` flag that drives the non-zero exit code — suitable for headless drift checks. ## Security / trust model - External sources are **untrusted**. Discovery and vendoring never execute upstream code; only file contents are read. - The shared read-only token limits blast radius; rotation is a single fleet run (see the handoff doc). - MCP fragments from vendored sources are **never activated** by the binary; merging and vault substitution remain an explicit fleet-controller decision. - Binary updates happen exclusively via pinned fleet rollouts — there is no self-update path to abuse. ## Troubleshooting | Symptom | Cause / fix | |---|---| | `error: … no client config found. Searched (in order): …` | No config anywhere. Create one where the error suggests, or skip it: `agent-lib sync --repo `. | | `error: … include entry "x" not found upstream` | Typo, or the item was renamed/removed upstream. Fix the lockfile include list, or re-add with a corrected `--include`. | | `error: … collides with own skills "x"` | Deployed-name collision. Add `--rename =` (add) or fix the rename map (update). | | `validate` reports tree/lockfile divergence | Someone edited `external/` by hand. Re-run `vendor update ` to restore the pinned state. | | Skill didn't update, `skipped (local modifications kept): 1` | Working as designed — you edited the deployed item. Restore the company version by deleting the local copy and re-syncing. | | `status` exits 1 | Local modifications block repository updates (or occupy names the repo needs). See `status` output for the item list. | | Sync fails with clone/fetch error | Work repository unreachable, or token expired. Previous local state remains intact; fix config/network and re-run. | | No toast on Windows | Toasts need an interactive user session — the scheduled task must run as the logged-in user, not SYSTEM. | ## Development ```sh make build # build with version ldflags make test # unit + black-box e2e (fully offline, local fixture git repos) make lint # go vet + gofmt check ./scripts/e2e-acceptance.sh # full spec walk: 60+ assertions, offline ``` Release: `goreleaser release` on a tagged commit builds all five targets with checksums and publishes to GitHub releases. Verify with `goreleaser release --snapshot --clean` or a tagged `--skip=publish` dry run. ## Scope and non-goals (v2) In scope: vendoring with selection/renames into any git work repository; OpenCode-only deployment with three-state protection; toast notifications; goreleaser/GitHub release pipeline. Not in scope: client-side selection or profiles, additional targets (Pi, Hermes, Crush, Amp), MCP merge/vault (fleet-controller concern), binary self-update, git submodule sources, migration tooling from the Python-era lockfile.