Effortless binary manager. Install, update, and organize standalone binaries pulled straight from release pages — no package manager, no build step.
geto downloads release assets from GitHub, GitLab, Codeberg, HashiCorp releases, Docker images, or go install, picks the right artifact for your OS/arch, unpacks it if needed, and keeps track of what's installed so you can update everything in one command.
A hard fork of marcosnils/bin with a single tagged config, repo descriptions, and a full TUI.
go install github.com/bresilla/geto/src@latestor build from source:
git clone https://github.com/bresilla/geto
cd geto
make build # produces ./getoOn first run, geto picks a download directory from your PATH (e.g. ~/.local/bin) and creates its config.
geto install github.com/sharkdp/bat # install (alias: add, i)
geto # launch the interactive TUI
geto list # plain table of everything
geto update # update the "default" tier
geto update bat # update a single binary
geto remove bat # uninstall (alias: rm, uninstall, delete)Running geto with no arguments opens the TUI on a real terminal, and falls back to list when piped.
| Command | Aliases | What it does |
|---|---|---|
install <url> [name|path] |
add, i |
Install a binary from a repo/URL |
update [name…] |
u |
Update binaries (default tier, or named ones) |
ensure [name…] |
e |
Reinstall anything missing or hash-mismatched |
list |
ls |
Print a table of managed binaries |
remove <name…> |
rm, uninstall, delete |
Delete the binary and forget it |
prune |
Forget entries whose files no longer exist | |
pin / unpin <name…> |
Freeze / unfreeze a binary's version | |
tag … |
Manage tags/tiers (see below) | |
describe [name…] |
Fetch & store repository descriptions |
Useful flags on update:
--dry-run— report what would update, change nothing-y, --yes— skip the confirmation prompt-r, --recheck— re-prompt for asset selection instead of reusing the remembered choice-c, --continue-on-error— keep going if one binary fails
Every binary has one or more tags. Untagged binaries belong to default. A persistent --tag/-t flag sets the tag context for any command:
geto install -t essential github.com/junegunn/fzf # install tagged "essential"
geto -t essential update # update only the "essential" tier
geto -t all list # everything, regardless of tag
geto update # == geto -t default update- No
--tag→ acts on thedefaulttier. --tag all→ acts on every binary.
Change tags after the fact:
geto tag ls # list tags and counts
geto tag show bat # show a binary's tags
geto tag add essential bat fzf # add a tag
geto tag rm essential bat # remove a tag (falls back to "default")geto stores each repo's one-line description in the manifest so the TUI can show it offline. New installs fetch it automatically; backfill existing entries with:
geto -t all describe # fetch descriptions for everything missing one
geto describe --force bat # refetch even if already presentFor private/rate-limited repos, export a token first (see Authentication).
Run geto (no args) to open the interactive UI: a full-width list with two-line entries showing name, version + update status, repo, architecture, libc (musl/glibc/static), size, tags, and the repo description.
| Key | Action |
|---|---|
↑/↓, j/k, g/G |
navigate |
/ |
fuzzy filter |
u |
update selected |
r |
check all for updates |
p |
pin / unpin |
e |
edit entry (URL, provider, tags, description) in a popup |
m |
forget the saved asset/archive choice for the selected binary |
o |
open the repository in your browser (xdg-open) |
d / x |
remove (with confirmation) |
t |
cycle the tag scope |
? |
toggle full help |
q |
quit |
On first run geto writes a config file. Colors are terminal palette indexes (0–255) or hex — so pywal-style tools recolor geto automatically, and the 232..255 grayscale ramp gives subtle row shading:
# foreground colors
accent = 1 text = 15 muted = 8
ok = 2 warn = 3 err = 9 tag = 6
# TUI row backgrounds (alternating + selected)
row_bg = 232
row_bg_alt = 235
row_bg_selected = 237| File | Purpose |
|---|---|
$XDG_CONFIG_HOME/geto/list.json |
Manifest — portable: path, url, provider, tags, description |
$XDG_STATE_HOME/geto/config.state.json |
State — per-machine: version, hash, package path, pinned, selected asset, cached description |
$XDG_CONFIG_HOME/geto/config |
TUI colors |
The manifest and per-machine state are kept separate so the manifest is safe to share or check into dotfiles. Config resolution honors $XDG_CONFIG_HOME, falling back to ~/.config/geto (or a legacy ~/.geto).
When run as root without explicit overrides, geto reads /etc/geto/list.json,
writes /var/lib/geto/config.state.json, and installs into /usr/local/bin.
| Provider | Example |
|---|---|
| GitHub | geto install github.com/cli/cli |
| GitLab | geto install gitlab.com/gitlab-org/cli |
| Codeberg | geto install codeberg.org/lukeflo/bibiman |
| HashiCorp | geto install releases.hashicorp.com/terraform |
| Docker | geto install docker://hashicorp/terraform |
go install |
geto install goinstall://github.com/x/y |
Asset selection scores candidates by OS/arch and filters out non-installable files (.sig, .sha256, .sbom, .deb, …). Your pick is remembered, so updates don't re-prompt unless the release's file layout changes (use update -r to force a re-pick).
geto ships a flake package plus NixOS and Home Manager modules. In Nix you
write a normal list of repositories; the module generates list.json, then runs
geto --tag all ensure. ensure downloads missing binaries and fills the
mutable state file with versions, hashes, remembered asset choices, and cached
repository descriptions.
{
inputs.geto.url = "github:bresilla/geto";
outputs = { nixpkgs, geto, ... }: {
nixosConfigurations.host = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
geto.nixosModules.default
{
programs.geto = {
enable = true;
entries = [
"github.com/rust-lang/mdBook"
{ repo = "github.com/git-town/git-town"; tag = "essential"; }
{ repo = "github.com/atuinsh/atuin"; name = "atuin"; tag = "shell"; }
];
};
}
];
};
};
}For Home Manager:
{
imports = [ inputs.geto.homeManagerModules.default ];
programs.geto = {
enable = true;
entries = [
"github.com/rust-lang/mdBook"
{ repo = "github.com/git-town/git-town"; tag = "essential"; }
];
};
}The generated manifest is just regular geto config:
{
"default_path": "/usr/local/bin",
"bins": {
"/usr/local/bin/git-town": {
"path": "/usr/local/bin/git-town",
"url": "github.com/git-town/git-town",
"tags": ["essential"],
"patch": true
}
}
}For the default root/system paths, create /etc/geto/list.json and run:
sudo geto --tag all ensureYou can also point geto at an explicit manifest/state/install directory:
GETO_CONFIG_FILE=/tmp/geto/list.json \
GETO_STATE_FILE=/tmp/geto/state.json \
GETO_DEFAULT_PATH=/tmp/geto/bin \
GETO_NONINTERACTIVE=1 \
geto --tag all ensureSet as needed in your environment:
GITHUB_AUTH_TOKENorGITHUB_TOKEN— GitHub API (avoids the 60 req/hr unauthenticated limit)CODEBERG_TOKEN— CodebergGHES_BASE_URL,GHES_UPLOAD_URL,GHES_AUTH_TOKEN— GitHub Enterprise
make build # build ./geto (version-stamped)
make install # install to $PREFIX/bin (default ~/.local/bin)
make run ARGS='list -t all'
make test # go test ./...
make verify # fmt-check + vet + test
make release TYPE=minor # cut a release via git-rel
make help # list all targetsMIT — see LICENSE. geto is a hard fork of
marcosnils/bin; see
ACKNOWLEDGMENTS.md.
