A lightweight, cross-platform binary manager written in Zig — a port of marcosnils/bin. It mirrors the references functionality: the same commands, flags, JSON config format and providers, built with zero runtime dependencies and Zig 0.15.2.
- Zig 0.15.2 or later (the code targets the 0.15.2 std library; newer 0.15.x patches work, 0.16+ is not supported).
# Windows / macOS / Linux
zig build -Doptimize=ReleaseSafe # binary at zig-out/bin/bin
# cross-compile to Linux from anywhere
zig build -Dtarget=x86_64-linux
zig build -Dtarget=aarch64-linuxzig build test runs the unit test suite (config, providers, assets, checksum).
On Windows you can also use just install-zig to fetch Zig 0.15.2 and
just build.
bin [command]
Commands:
ensure Ensures that all binaries listed in the configuration are present
install Installs the specified binary from a url
list List binaries managed by bin
pin Pins current version of the binaries
prune Prunes binaries that no longer exist in the system
remove Removes binaries managed by bin
unpin Unpins current version of the binaries
update Updates one or multiple binaries managed by bin
clean Clears the download cache (zig extension)
info Shows API rate limit information (zig extension)
Flags:
--debug Enable debug mode
-h, --help help for bin
-v, --version version for bin
Running bin with no arguments lists the managed binaries. Aliases match the
reference: install/i, update/u, ensure/e, list/ls,
remove/rm.
bin install <url> [name | path] [-f] [-a] [-p provider] [-n pattern]
-f, --forceoverwrite the file if it already exists-a, --allshow all possible download options (skip scoring & filtering)-p, --providerforce a specific provider (github, gitlab, codeberg, hashicorp, helm, goinstall, docker)-n, --nameglob pattern selecting a specific asset (useasset/fileto select inside archives)
The second argument is a file name (joined with the default download path) or a path. Supported URL forms:
bin install https://github.com/cli/cli
bin install github.com/junegunn/fzf
bin install gitlab.com/gitlab-org/cli
bin install codeberg.org/mergiraf/mergiraf
bin install releases.hashicorp.com/terraform
bin install get.helm.sh/helm-v3.16.3-linux-amd64.tar.gz
bin install goinstall://github.com/charmbracelet/glow
bin install docker://hashicorp/terraformSpecific versions can be pinned with an @tag suffix
(bin install github.com/junegunn/[email protected]) — unlike the reference, the
@tag is parsed out of the repo name so updates keep working (the
"breaking updates" fix).
bin update [binary_path...] [--dry-run] [-y] [-a] [-p] [-c] [-x pattern...]
Checks for newer versions (semver-aware), asks for confirmation, then
re-installs. --dry-run exits with code 3 when updates are found, -y skips
the prompt, -c continues on error, -x excludes binaries.
bin ensurere-installs binaries whose file is missing or whose SHA-256 no longer matches the stored hash (keeps the pinned state).bin pin <name|path...>/bin unpin— pinned binaries are skipped byupdate(unless explicitly listed).bin prune [-f]removes config entries for binaries missing from disk (asks for confirmation unless-f).bin remove <name|path...>removes the binary and its config entry.
The configuration is JSON, byte-compatible with the reference implementation:
{
"default_path": "/home/user/.local/bin",
"bins": {
"/home/user/.local/bin/gh": {
"path": "/home/user/.local/bin/gh",
"remote_name": "gh",
"version": "v2.40.0",
"hash": "ae2a4e100870f9798359c035f6338add9e5dcc727545e7daa110acfa4a03e979",
"url": "github.com/cli/cli",
"provider": "github",
"package_path": "bin/gh",
"selected_asset": "gh_2.40.0_linux_amd64.tar.gz",
"pinned": false
}
}
}Resolution order (same as the reference):
BIN_CONFIGenvironment variable (the file must exist)$HOME/.bin/config.json(legacy location)$XDG_CONFIG_HOME/bin/config.jsonwhenXDG_CONFIG_HOMEis set$HOME/.config/bin/config.jsonwhen$HOME/.configexists- default
$HOME/.bin/config.json
On first run the default download path is auto-detected from the first
writable directory in PATH (interactively picked, or prompted for manually).
Paths in the config may contain $VAR/${VAR} expansions.
Auth tokens are read from the environment (same names as the reference):
GITHUB_TOKEN (or GITHUB_AUTH_TOKEN), GITLAB_TOKEN (plus
GITLAB_TOKEN_<hostname> for self-hosted), CODEBERG_TOKEN, and the GHES
triple GHES_BASE_URL/GHES_UPLOAD_URL/GHES_AUTH_TOKEN.
- github — release assets;
?filter=glob over release tags supported - gitlab — project packages, release asset links and release-description links; self-hosted instances via the URL hostname
- codeberg — Gitea/Forgejo releases; self-hosted instances supported
- hashicorp —
releases.hashicorp.com(semver-aware latest) - helm —
get.helm.sh(static platform matrix) - goinstall — builds a Go module via
go install module@version - docker —
docker://images; installs a wrapper script that runs the image with the current directory mounted (the pull shells out to thedockerCLI rather than the daemon SDK)
- The update bug fixed here (never upstreamed):
user/repo@tagURLs no longer breakbin update— the tag is split from the repo with the last@, and short/domain/full URL forms all round-trip correctly. cleanandinfoare zig extensions (not present in the reference).bzip2-compressed releases require abzip2binary on PATH (the Zig std library dropped bzip2 in 0.15).- Transfers have a deadline. The reference blocks on the socket forever when a
peer stops sending data mid-transfer (no output, no error); here a transfer
that receives nothing for
--timeout <seconds>(default 30,0disables, orBIN_TIMEOUT) is retried up to--retries <n>extra times (default 2,BIN_RETRIES) with a small backoff. Each attempt runs on its own thread and a stalled one is given up on rather than waited for, then retried on a fresh connection — which is what makes the retry work on Windows too, where a blocked Winsock receive cannot be woken from another thread. Because of that, an abandoned attempt can still be inside the HTTP client, so clients (and the stalled attempt's thread, parked in the kernel) live untilbinexits.
Releases are tagged vX.Y.Z, starting at v1.0.0 — the port matches the
reference's feature set (marcosnils/bin v0.29.3) plus the fixes listed above, so
it is no longer a moving dev build. bin -v/--version prints the version from
src/version.zig; bump that and .version in build.zig.zon together.
MIT