vendor select <name> --add/--remove/--mode all changes what a source contributes without advancing the pinned rev: ids are validated against the pinned upstream (typos abort listing available ids), collisions re-run, renames from the lockfile are honored, and the external area is rewritten via the same staging-swap as update. include mode grows and shrinks the include list; all mode --remove moves ids onto exclude; --mode all resets to everything. Failed selects mutate nothing. Closes beads: agent-lib-cd3
agent-lib
One Go binary, two roles: curator and client.
- Curator side —
agent-lib vendor …pulls external git repositories into a plain work repository underexternal/<source>/<type>/, pinned by revision, with per-source selection and collision-safe naming. Publication is nothing more thangit commit && git push. - Client side —
agent-lib syncpulls 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, verify the
checksum, and put it on your PATH:
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. 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/<id>/SKILL.md own skills (folder artifacts)
commands/<id>.md own OpenCode commands
agents/<id>.md own OpenCode agents
mcp/<id>.yaml own MCP fragments (never deployed by the binary)
external/<source>/<type>/ vendored content, mirrors the same four types
agent-lib.lock.json lockfile v2 — pins every source
Vendoring a source
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 <branch-or-tag> 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,cswitches 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 selectandvalidate:allcombined 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 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):
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:
--addappends ids (validated against the pinned upstream — typos abort with the list of available ids),--removedrops ids and deletes their folders fromexternal/. - all mode:
--removemoves ids onto the exclude list (lockfile v2 supports exclude in all mode) and deletes their folders;--addis a hard error — everything is already selected. --mode allclears 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:
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
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
agent-lib validate # offline: rules + tree/lockfile consistency
agent-lib vendor list # sources with pin, mode, item counts
agent-lib vendor inspect <name> # 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:
agent-lib sync # pull the work repository and deploy
agent-lib status # report local state; exit 1 when updates are blocked
Both accept --repo <url> / --ref <ref> for config-free personal use —
see Configuration below.
Deployment mapping (OpenCode target):
| Type | Destination |
|---|---|
| skills | ~/.agents/skills/<id>/ (whole artifact folders) |
| commands | ~/.config/opencode/commands/<id>.md |
| agents | ~/.config/opencode/agents/<id>.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:
--repo <url>flag (works with no config file at all)--config <path>flagAGENT_LIB_CONFIGenv- user-level config (personal machines)
- 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.
{
"repo_url": "https://git.example.com/company/agent-content.git",
"ref": "main",
"token": "<read-only deploy 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):
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 <url>. |
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 <upstream>=<deployed> (add) or fix the rename map (update). |
validate reports tree/lockfile divergence |
Someone edited external/ by hand. Re-run vendor update <source> 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
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.