Files
dotfiles/.agents/skills/add-cli-tool/SKILL.md
T
noconnorandClaude Opus 4.8 485e267565 .agents/: add skills for working on this repo
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>
2026-07-13 13:10:18 -04:00

2.9 KiB

name, description
name description
add-cli-tool 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:

# <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."