Provision, verify, pin, and update external CLI binaries from Go.
Some tools you depend on aren't written in Go, so go tool can't manage them. binkit fetches
them from GitHub releases, verifies them against a pinned SHA-256, caches them per version,
and hands your program back a path.
path, err := resolver.Ensure(ctx, catalog.Typst)
if err != nil {
return err
}
cmd := exec.CommandContext(ctx, path, "compile", "in.typ", "out.pdf")go.mod has exactly one require line: github.com/ulikunitz/xz, which is itself
stdlib-only with zero transitive dependencies.
That dependency exists for one reason — many projects (Typst among them) ship .tar.xz, and
the Go standard library has archive/tar, archive/zip, and compress/gzip but no xz
decoder. Shelling out to tar -xJf would trade a compile-time dependency for a runtime
environment dependency and break on Windows and minimal containers, so it isn't done.
Everything else is stdlib or deliberately hand-rolled: semver comparison instead of
golang.org/x/mod, TTY detection instead of mattn/go-isatty, atomic directory rename
instead of a file-locking library.
tools.json lives in your repo and is checked in:
{
"typst": {
"version": "0.15.1",
"repo": "typst/typst",
"digests": {
"linux/amd64": "sha256:...",
"darwin/arm64": "sha256:...",
"windows/amd64": "sha256:..."
}
}
}Digests are captured for every platform at pin time, so a colleague on macOS gets a verified install from a lockfile generated on Linux.
Ensure never resolves "latest" and never silently reaches the network — a pinned version
downloads by direct URL, which also means no GitHub API rate limit and no token. Only an
explicit Update call changes the pin.
Once every seven days, binkit checks whether a newer release exists and reports it — to stderr, and only when stderr is a terminal, so it never pollutes piped output or CI logs. When there is nowhere to show a notice the check is skipped entirely rather than performed and discarded.
binkit: typst 0.15.1 is pinned; 0.16.0 is available.
run: massprep --update-tools typst
The second line appears only if you set Resolver.UpdateHint — binkit cannot know your
CLI's flags.
A failed check backs off for an hour rather than consuming the seven-day window, so being
offline for a week neither retries on every invocation nor silences the next real check.
Nothing here can fail a build, and nothing is ever updated automatically. Set
BINKIT_NO_UPDATE_CHECK=1, or Resolver.NoCheck, to disable.
BINKIT_<TOOLNAME> (e.g. BINKIT_TYPST=/usr/bin/typst) short-circuits everything and uses
that binary. Useful for CI images, Nix, and distro packages.
MIT