| name | add-self-update |
| description | Add a self-update command to an agent-first CLI that checks a server version endpoint, downloads, verifies (content hash + smoke test), and atomically swaps the binary/bundle in place. Use when asked to "add self-update", "add auto-update", "add an update command", or make a CLI able to update itself. |
Add self-update to a CLI (cli-update-spec convention)
Give a CLI an update subcommand that checks the server's latest version (content hash),
downloads, verifies, smoke-tests, and atomically swaps — with a passive nudge when stale.
Full norms in PROTOCOL.md; full how-to in RECIPE.md.
This skill is the checklist.
Decide the shape first
- Go binary (single file): atomic rename + optional
syscall.Exec for in-place re-exec.
See mago as the worked example.
- Bash CLI + bundle (tarball): directory swap (old →
.bak, new → in place). See
grepapi as the worked example.
- Panel-driven (worker daemons): the panel sends a WebSocket update command with URL +
SHA256; the worker downloads, verifies, swaps, and systemd restarts. See
automaintainer.
The four steps
-
Version endpoint GET /version (or /v1/version): public, no auth. Hash the
artifact on disk (sha256[:12]), return {ok, version, download, sha256}. Copy the
handler from RECIPE.md step 1 and route it.
-
update CLI command — the heart:
myapp update [--check] [--force]
- compute local version (
sha256[:12] of the binary, or read VERSION file for
tarball CLIs);
- fetch
GET /version, compare;
- if same and not
--force → "up to date", exit 0;
- if
--check and different → report, exit 5;
- download to temp → verify
sha256[:12] matches → smoke-test (<tool> version
runs) → atomic swap (current → .bak, new → in place) → write VERSION;
- on any failure after swap starts → restore from
.bak, exit 100.
-
Passive nudge: on server-hitting commands (not version/help/doctor), fetch
/version (3s timeout, throttled to once/hour via a cache file), print to stderr if
stale. Never blocks, never fails.
-
Installer: write an install.sh that downloads, computes the content hash, extracts,
writes VERSION, symlinks into PATH, smoke-tests. Tell users: "To update later:
myapp update".
Verify before you're done
curl -s https://myapp.example.com/v1/version | jq .
myapp update --check
myapp update --check
myapp update
myapp version
myapp update
myapp leads
Gotchas
- Never skip the smoke test. A self-consistent partial publish passes the hash check
but bricks the tool. Running
<tool> version catches it before the swap. (This bricked
rbm21 in mago's early days.)
- Always keep a
.bak. If the swap fails or the new binary is bad, the operator needs
a one-command rollback (mv ~/.tool.bak ~/.tool).
- No silent auto-update. The default is manual (nudge only). Auto-update is opt-in.
- Bash quoting: avoid
( in usage strings inside case patterns (parsed as pattern
separators). Avoid apostrophes inside jq single-quoted filters (breaks the bash quote).
- Concurrent updates: use a per-PID temp file + atomic rename. Last-wins is safe
because both write identical bytes.