Your CLI Should Update Itself
I have 6 servers running remotecmd daemons. Last week I spent 20 minutes manually SSHing into each one, downloading the new binary, swapping it, and restarting the daemon. Six machines, three commands each, two of them needed root which I didn't have, one had a stale PID file.
That's stupid.
So I implemented remotecmd-cli update. One command. Downloads the latest release, verifies the SHA256, smoke-tests the binary, atomically swaps it with a .bak rollback. The next time I need to update 6 servers, I run rcmd exec --targets all --cmd "remotecmd-cli update" and I'm done in 30 seconds.
The problem with manual updates
If you distribute a CLI binary — Go, Rust, whatever — your users update it by:
- Remembering to check for a new version (they won't).
- Going to your GitHub releases page.
- Downloading the right binary for their platform.
- Copying it over the old one.
- Hoping they got the right architecture.
Nobody does this. So your users run stale versions forever. They hit bugs you fixed months ago. They miss features you shipped last week. And when they finally do update, they've forgotten how.
AI agents have it worse. An agent landing on a machine has no idea what version is installed, no idea a newer one exists, and no way to update it even if it did. It just runs the stale binary and works around the missing features.
The cli-update-spec
I wrote a spec for this — one of four agent-first CLI specs that make any CLI self-updating, self-describing, and self-reporting.
The update spec is simple. Three parts:
1. A version endpoint
Your server exposes GET /version — public, no auth. Returns the content hash of the latest artifact:
{"ok":true,"version":"a1b2c3d4e5f6","download":"/dl/myapp","sha256":"..."}The version is sha256[:12] of the binary. No semver discipline needed — if nothing changed, the hash is the same, and no update is triggered.
2. An update command
myapp update [--check] [--force]
The flow is: check → download → verify → smoke-test → swap.
- Check: compare local hash to server hash. Same? Exit 0. Different? Exit 5 (with
--check) or proceed. - Download: fetch the new binary to a temp file.
- Verify: SHA256 of the download must match the server's advertised hash. Mismatch? Exit 100, delete the temp file, live binary untouched.
- Smoke-test: run
<new-binary> version. If it doesn't produce valid output, reject it. This catches the case where the server hashed a partially-written file — the hash matches the truncated file, but the binary is broken. - Atomic swap: current →
.bak, new → in place. If the swap fails, restore from.bak.
The .bak stays around. If the new binary is bad, rollback is mv ~/.myapp.bak ~/.myapp.
3. A passive nudge
On any command that hits the server, the CLI opportunistically fetches /version (3-second timeout, throttled to once per hour) and prints to stderr if a newer version exists:
[update] a newer remotecmd-cli is available (1.5.0 → 1.6.0). Run: remotecmd-cli updateNever blocks. Never fails. Never auto-updates. Just a nudge.
How remotecmd does it
remotecmd is a Go binary distributed via GitHub Releases. The implementation is ~250 lines of Go in update.go:
- Version source: GitHub Releases API (
api.github.com/repos/javimosch/remotecmd-cli/releases/latest) - Platform detection:
runtime.GOOS + runtime.GOARCH→ picks the right asset - Checksums: downloads
checksums.txtfrom the release, verifies the binary hash matches - Smoke test: runs
<new-binary> version, checks output containsremotecmd-cli version - Swap:
os.Rename(exe, exe+".bak")thenos.Rename(tmp, exe)— atomic on the same filesystem - Nudge: goroutine on server-hitting commands, throttled via a file in
~/.remotecmd/nudge-check
No external dependencies. No package manager. No installer re-run. Just the binary updating itself.
The bootstrapping problem
There's one catch: update can only work if the binary already has the update command. If you're running v1.5.0 (which didn't have self-update), you can't self-update to v1.6.0.
The solution is mundane: the first update is manual. After that, every future update is one command. This is the same pattern as every package manager — you install apt once, then apt upgrade forever.
For remotecmd, I did the bootstrap update on 6 servers manually (SSH + curl + chmod). Now they're all on v1.6.0, and the next time I ship v1.7.0, I can update all of them with:
rcmd exec --targets dk1,dk2,rbm20,rbm21,pve2,vps1 --cmd "remotecmd-cli update"One command, six servers, 30 seconds. That's the point.
Why not just use a package manager?
Package managers are great. But:
- Not every tool deserves a Homebrew formula. If you ship a single binary, a self-update command is simpler than maintaining packaging metadata for 5 ecosystems.
- AI agents can't run
brew upgrade. They can runmyapp update. The command is part of the tool's own interface — discoverable viamyapp help, documented inmyapp guide, callable from any context. - Servers don't have package managers. A Docker container, an LXC container, a minimal VPS — they have your binary, not your apt repo.
- Content hash > semver for updates. No version-bump discipline needed. If the artifact didn't change, the hash is the same, and no update is triggered. You can re-publish the same version and it's a no-op.
The smoke test saved me already
The spec says: after downloading and verifying the hash, run <new-binary> version before swapping. This catches the case where the server hashed a partially-written file — the hash matches the truncated file, but the binary is broken.
I didn't think this would happen. Then it did. Not in remotecmd (GitHub Releases are atomic), but in mago, where the version endpoint serves a binary from a directory that another process writes to. A race between the publish and the hash computation served a truncated file that passed the hash check but would have bricked the tool. The smoke test caught it.
The smoke test is not theoretical. It is the belt to the hash's suspenders.
Try it
If you ship a CLI, add update. The spec is MIT, language-neutral, ~30 minutes to implement. The recipe has copy-pasteable code for Go and bash.
If you use remotecmd, update is in v1.6.0:
remotecmd-cli update --check # see if there's a newer version
remotecmd-cli update # download, verify, smoke-test, swapYour users — and your AI agents — will thank you.