- skills are found at any depth under the skills dir, so category-nested layouts (skills/<category>/<id>/SKILL.md) vendor correctly; the folder basename is the upstream id and the outermost SKILL.md wins over anything nested inside a skill folder - web-tree urls pointing directly at a type directory (.../tree/main/skills) now map that directory as the type root instead of nesting it under the default layout - verified against the real github.com/mattpocock/skills repository (36 skills across categories; include-selection by basename)
226 lines
9.4 KiB
Markdown
226 lines
9.4 KiB
Markdown
# 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/<source>/<type>/`, 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/<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
|
|
|
|
```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 <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,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` 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
|
|
|
|
### 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 <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:
|
|
|
|
```sh
|
|
agent-lib sync # pull the work repository and deploy
|
|
agent-lib status # report local state; exit 1 when updates are blocked
|
|
```
|
|
|
|
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 client config is admin-provided (fleet rollout), JSON at
|
|
`C:\ProgramData\agent-lib\config.json` on Windows, `/etc/agent-lib/config.json`
|
|
elsewhere. Override per run with `--config` or `AGENT_LIB_CONFIG`:
|
|
|
|
```json
|
|
{
|
|
"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.
|
|
|
|
### 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: … 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
|
|
|
|
```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.
|