Reproducible, per‑repo installs of infra / DevOps CLI tools (terraform, kubectl, helm, gh, buf, jq, node, pnpm, etc.) without polluting your global PATH.
tlk lets a repository declare the exact external command‑line tools it depends on in a simple tlk.toml. A lock file (tlk.lock) captures resolved versions + source URLs. Teammates (and CI) run tlk install to materialize a private .tlk/bin folder that is auto‑activated by a lightweight shell hook. No more “which version of terraform do I need?” messages.
It’s like a package.json + lock for the miscellaneous non-language CLIs you need to build/deploy/test… and nothing else. Bring your own language toolchain manager; tlk focuses on the rest.
Download latest release and add to your path: https://github.com/codyspate/tool-locker/releases
Convienience Script
This script also installs a updater tlk-update to update tlk itself.
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/codyspate/tool-locker/releases/download/v0.1.6/tlk-installer.sh | sh
Or Build:
git clone https://github.com/codyspate/tool-locker.git
cd tool_locker
cargo build --release
./target/release/tlk --helptlk install terraformCreates .tlk/bin and downloads each tool (parallelized when >1). Writes / updates tlk.lock with exact versions and fully rendered URLs.
eval "$(tlk hook)" # bash / zshNow when you cd into a directory containing tlk.toml, that repo’s .tlk/bin is transparently prepended to PATH for the shell session.
Commit both tlk.toml and tlk.lock.
terraform --verison| Pain | tlk Answer |
|---|---|
| Onboarding friction (“install these 7 tools, specific versions”) | One command after clone: tlk install |
| Global version drift (Homebrew / chocolatey upgrades behind your back) | Explicit versions/ranges, concretized & locked |
| Cross‑platform URL differences | Normalized {os} / {arch} placeholders; multi‑platform sources in lock |
| “Works on my machine” infra tooling | Per‑repo sandbox .tlk/bin, not global PATH |
| CI reproducibility | tlk install --locked guarantees lock fidelity |
| Ad‑hoc curl | Manual URLs become declarative entries with optional checksums |
Core ideas:
- Declarative desired tool specs (
tlk.toml). - Deterministic resolution -> concrete download URL(s).
- Idempotent installer (skips unchanged versions).
- Lock file capturing exact resolved version + rendered URL and platform matrix.
- Zero global side effects: everything lives under project root (unless you purposely use
setup).
Two syntaxes coexist:
- Shorthand (for known tools) – single line:
terraform = "1.8.5"or ranges like^1.8.0orlatest. - Full table (for custom / advanced) under
[tools.<name>]with fields:version(string; can be range for known tools, but custom entries should be concrete)source(URL template; supports{version},{os},{arch})kind=archive|direct(defaults to archive)binary(path inside archive; omitted for direct downloads or auto‑detected for some known tools)sha256(optional explicit checksum of the archive / binary)per_osandper_os_archoverride maps for differing naming conventions (see code for full shape)
Placeholders:
| Token | Values |
|---|---|
{os} |
linux, darwin, windows |
{arch} |
amd64, arm64 |
{version} |
The resolved exact version |
If you provide both generic source and more specific per_os / per_os_arch, specificity wins (per‑OS+arch > per‑OS > generic).
Legacy [[tools]] array form is still accepted; run tlk migrate-config to upgrade to the [tools.<name>] style.
terraform, kubectl, helm, gh, buf, node, pnpm, yarn, just, jq, cosign, age, moon.
Each has logic for platform naming quirks (e.g. node’s x64 vs amd64) and implicit binary paths when they aren’t at archive root.
| Command | What it does |
|---|---|
tlk install |
Install or update all declared tools (writes/updates lock unless --no-lock) |
tlk install terraform@1.7.5 helm@latest |
Ad‑hoc install of specific known tool specs (bypasses tlk.toml entries for those) |
tlk install --locked |
Reinstall exactly what’s in tlk.lock (no writes) |
tlk plan |
Dry run: show planned names, versions, base URLs/templates |
tlk list |
Show desired vs installed versions (parse --version output) |
tlk verify |
Validate tlk.lock vs config + binaries (digest / checksum) |
tlk uninstall <name> |
Remove tool + config + lock entry |
tlk hook |
Emit shell hook (eval it) |
tlk setup |
One‑time create a global ~/.tlk/bin (future use) |
tlk migrate-lock |
Regenerate lock at latest schema & platform matrix |
tlk migrate-config |
Rewrite legacy [[tools]] syntax to new table style |
tlk diagnose --kind missing-platforms |
Spot tools lacking multi‑platform entries in lock |
Useful flags:
| Flag | Meaning |
|---|---|
--no-lock |
Skip creating/updating tlk.lock on install |
--locked |
Disallow resolution; only use already locked entries |
--no-verify |
Skip pre‑install verification (speed vs safety) |
--exact |
When installing specs, store exact instead of caret range |
For shorthand known tools you may supply:
- Exact semver:
1.2.3 - Caret / tilde:
^1.2.3,~1.2.0 - Partial / wildcard:
1.2.x,1.x,^1 latest- Complex OR / hyphen ranges (limited support):
1.2.x || 1.3.x,1.2.0 - 1.4.5
Canonicalization logic rewrites what’s stored back into config (when adding via specs) so teammates see the intended constraint (e.g. 1.2.3 becomes ^1.2.3 unless --exact used). The lock file always records the concrete chosen version.
Custom [tools.<name>] entries should generally provide exact versions (range satisfaction for arbitrary URLs is not yet implemented).
Schema (v3) stores for each tool:
version– resolved exact versionrequested_version– original range / spec (if different)source– concrete URL used for the current platformsource_template– the template (with placeholders)sources– matrix of rendered platform URLs when placeholders are present (linux/darwin/windows × amd64/arm64)sha256– optional checksum copied from configdigest– SHA256 of the installed binary (post‑extraction)
tlk verify re-renders expected URLs and compares digests & checksums so CI can catch drift or tampering. Use tlk install --locked to fail fast if config references versions not present in the lock.
Two patterns:
- Ephemeral PATH adjustment after install (
tlkattempts to prepend.tlk/binto its own process PATH for immediate use). - Persistent dynamic hook (
eval "$(tlk hook)") that trackscdevents and toggles PATH accordingly. Remove it => no global pollution.
Fish / PowerShell variants available via --shell.
Add a new known tool at latest:
tlk install just@latest --no-lock # fetch latest
tlk install --no-verify # install others if needed
tlk install --locked || tlk install # then lock (or simply rerun without --no-lock)Update a range (^1.8.5) to absorb new patch/minor:
tlk install # will fetch newer if within rangePin an exact version for reproducibility audit:
tlk install terraform@1.8.7 --exactUninstall a tool:
tlk uninstall jqVerify before commit / in CI:
tlk verify && tlk install --locked| Tool / Approach | Where tlk fits |
|---|---|
| asdf / mise | Those manage language runtimes (and via plugins, other tools). tlk is zero‑plugin, fast, file‑driven; simpler surface, fewer moving parts. |
| Homebrew / apt / choco | System package managers; mutate global state; slower upgrades; no per‑repo isolation. Pair nicely: use them only for tlk binary itself. |
| Nix / Devbox | Extremely reproducible; steeper learning curve and larger conceptual surface. tlk is intentionally narrow / lightweight. |
| Docker images | Great for hermetic builds but slower for local iterative CLIs; tlk keeps native performance. |
Tradeoffs / current limitations:
- Limited “latest version listing” support (only for subset of known tools: terraform, helm, gh, buf, kubectl).
- No automatic checksum lookups (you must provide
sha256manually if you want strict verification beyond digest of downloaded artifact). - Range semantics best‑effort; extremely complex compound ranges may resolve unexpectedly.
- Not a general artifact cache / mirror (no offline mode yet).
- Windows support is present but less battle‑tested than Linux/macOS.
sha256 (config) vs digest (lock) – The former is a known good provided by you (or upstream release notes). The latter is the hash of what was actually installed. Add sha256 for critical tools to catch supply chain tampering at download time; digest then confirms the stored binary hasn’t changed since locking.
Recommendations:
- For security‑sensitive binaries (e.g.
cosign), copy upstream published SHA256 and add to your entry. - Run
tlk verifyin CI. - Consider commit signing of lock file changes in high‑assurance environments.
Future ideas: automated checksum retrieval, optional signature verification (e.g., cosign attestations), offline cache.
Rust workspace with a single cli crate. Core modules:
config.rs– Parsetlk.toml, merging shorthand and custom entries; supports legacy repair.known_tools.rs– Catalog of built‑in tool recipes (templated or custom URL generators) + platform detection.installer.rs– Parallel download & extraction, verification, digesting, path refresh.lock.rs– v3 lock file schema + legacy upgrade.versioning.rs– Fetch & cache version lists (GitHub / HashiCorp scraping) for “latest” & range resolution.command_handlers/*– Thin orchestration for each subcommand (install, migrate, diagnose, etc.).platform/*– OS abstractions (permissions, naming, windows vs unix differences).
Install flow summary:
tlk.toml -> parse -> Tool structs -> (verify lock) -> parallel fetch -> extract -> compute digest -> write/update lock
- Offline / local cache (avoid re-downloading unchanged archives across repos).
- Checksum auto‑discovery & signature verification.
- Optional global registry of “recipes” discoverable from config.
- Richer
plandiff (what’s changing & why). - JSON output for machine integration (
--format json). - Built‑in update helper (bump locked versions satisfying ranges).
- More known tools (PRs welcome – keep curated, low maintenance).
- Open an issue describing the change / addition.
- Keep PRs focused; include rationale in the description.
- Update this README for user‑visible changes.
- Add or adjust tests (when they’re introduced) for lock / install logic.
Feeling adventurous? Prototype a feature behind a hidden flag and open a discussion.
Dual licensed under either of
- MIT license
- Apache License, Version 2.0
at your option.
Happy locking! 🔐