Five skills covering this repo's own conventions: adding a bootstrap module, adding a CLI to bin/, bumping a pinned version, adding an MCP server, and verifying a module change. Lives in .agents/skills/ because opencode discovers that path natively alongside .opencode/skills/ and .claude/skills/, making it the most tool-neutral of the three. Distinct from the repo's skills/ tree, which ships to every covert-gpt user via sync.sh; these stay in the checkout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
73 lines
2.9 KiB
Markdown
73 lines
2.9 KiB
Markdown
---
|
|
name: add-cli-tool
|
|
description: Use when adding a userland CLI binary to bin/install.sh (jq, kubectl, helm, k9s, stern, yq, tofu, task, op, opencode, …). Covers the install_<name> function pattern, arch tokens, the .versions idempotency stamp, and when a tool does NOT belong in bin/.
|
|
---
|
|
|
|
# Adding a CLI tool to `bin/`
|
|
|
|
`bin/install.sh` installs self-contained release binaries into `~/.local/bin`:
|
|
no sudo, nothing outside `$HOME`, so tools persist across Kasm redeploys.
|
|
|
|
## Does it belong here?
|
|
|
|
`bin/` is for **single, statically linked binaries**. If the tool is a tree
|
|
(interpreter + libraries), it does not fit `install_*_bin` and needs its own
|
|
module that unpacks into `~/.local/opt/<name>` and symlinks the CLIs — that is
|
|
exactly why `ansible/` and `node/` exist. Prefer static builds: after a Kasm
|
|
redeploy the root filesystem's shared libraries are gone, and a dynamically
|
|
linked binary in `$HOME` will break.
|
|
|
|
## The pattern
|
|
|
|
One `install_<name>` function, pinned version, guard, install, stamp:
|
|
|
|
```sh
|
|
# <name> — one line on what it is. Note any asset-naming quirk.
|
|
install_<name>() {
|
|
ver=v1.2.3
|
|
have_version <name> "$ver" && { fsay "<name> $ver already installed."; return 0; }
|
|
install_tarball_bin \
|
|
"https://github.com/<org>/<name>/releases/download/$ver/<name>_linux_$DEB.tar.gz" \
|
|
"<path/inside/archive>" <name>
|
|
mark_version <name> "$ver"
|
|
}
|
|
```
|
|
|
|
Then call it at the bottom of the file with the others.
|
|
|
|
**Keep `ver` byte-identical to the upstream tag.** Some tools tag `v1.2.3`,
|
|
some `1.2.3` (jq uses a bare `1.8.2`). `have_version` does a string compare
|
|
against the stamp in `~/.local/bin/.versions`, so a mismatch just means the
|
|
tool silently reinstalls on every bootstrap.
|
|
|
|
## Arch tokens
|
|
|
|
`bin/install.sh` precomputes both spellings — pick the one the upstream asset
|
|
actually uses, don't call `uname` yourself:
|
|
|
|
- `$ARCH` → `x86_64` / `aarch64` (rust-style release names)
|
|
- `$DEB` → `amd64` / `arm64` (Go/Debian-style — most devops tools)
|
|
|
|
## Picking the helper
|
|
|
|
- `install_raw_bin <url> <dest>` — the asset *is* the binary (jq, kubectl, yq).
|
|
- `install_tarball_bin <url> <path-in-archive> <dest>` — `.tar.gz` (helm, k9s).
|
|
- `install_zip_bin <url> <path-in-archive> <dest>` — `.zip` (the 1Password CLI).
|
|
|
|
Asset names are inconsistent and this is where it usually goes wrong: k9s
|
|
capitalizes `Linux`, helm nests under `linux-$DEB/`. Fetch the release page and
|
|
read the real asset name rather than guessing — a 404 from `curl -fsSL` will
|
|
abort bootstrap under `set -eu`.
|
|
|
|
## Conditional installs
|
|
|
|
A tool that only works in some environments gets a gate, like kind — installed
|
|
only when a **rootful** Docker daemon answers, because it does not work against
|
|
rootless Docker on Kasm. Reuse `docker_full_available()` rather than writing a
|
|
new probe.
|
|
|
|
## Finally
|
|
|
|
Add the tool to the `bin/` bullet in `README.md`. Then run it twice and confirm
|
|
the second run prints "already installed."
|