A package manager that installs portable apps from GitHub Releases, using gh as its primary interface to GitHub.
Linux / macOS:
curl --fail-with-body --location --no-progress-meter --url https://raw.githubusercontent.com/meop/ghpm/main/install.sh | shWindows:
irm -ErrorAction Stop -Uri https://raw.githubusercontent.com/meop/ghpm/main/install.ps1 | iexFrom source:
go install github.com/meop/ghpm/cmd/ghpm@latestAfter installing, add ~/.ghpm/bin to your PATH.
ghpm add fzf # install latest fzf
ghpm add fzf@14 # install latest fzf 14.x (tracks within major)
ghpm add fzf@14.1 # install latest fzf 14.1.x (tracks within minor)
ghpm add fzf@14.1.0 # install exact fzf 14.1.0 (static, never updates)
ghpm add fzf ripgrep bat # install multiple in parallel
ghpm add --reinstall fzf # reinstall even if already installed
ghpm list # show installed packages
ghpm list fzf rip # show only packages a term starts a word of
ghpm find fzf # search cached repos by name, alias, source or description
ghpm find regex # ...when what you remember is what the tool does
ghpm info fzf # show available releases and assets
ghpm outdated # check for updates
ghpm outdated fzf # check updates for only packages a term starts a word of
ghpm sync # update all floating and major/minor-pinned packages
ghpm sync fzf # update specific package
ghpm sync --reinstall # reinstall all packages even if already at latest version
ghpm sync --reinstall fzf # reinstall specific package even if already at latest version
ghpm download fzf # download release asset to cache without installing
ghpm download --path /tmp fzf # download release asset to a specific directory
ghpm remove fzf # remove package
ghpm tidy # remove unused cached assets and orphaned package dirs
ghpm tidy --all # remove all cached assets
ghpm upgrade # upgrade ghpm itself
ghpm refresh # refresh repo sources to latest versions
ghpm doctor # check system health| Flag | Description |
|---|---|
--dry-run, -n |
Print what would be done without executing |
--yes, -y |
Skip confirmation prompts |
--verbose, -v |
Also print the checks and syncs of ghpm's vendored tools, and more detail on their errors |
--non-interactive |
Never prompt; for scripted use |
| Flag | Commands | Description |
|---|---|---|
--reinstall, -r |
add, sync |
Reinstall even if already installed / already at latest version |
--force, -f |
add, sync |
Replace a file in ~/.ghpm/bin that ghpm did not put there |
--path |
download |
Destination directory (default: ~/.ghpm/download/) |
--all |
tidy |
Remove all cached assets regardless of installation status |
--long-names, -l |
list, find, outdated |
Print names only, one per line, and nothing when there are none |
--short-names, -s |
list, find, outdated |
Print names only, space-separated on one line, and nothing when there are none |
--skip-hash-check |
add, sync, download |
Skip SHA256 hash verification of downloaded assets |
| Syntax | Meaning | Updates |
|---|---|---|
fzf |
Latest version | Yes — ghpm sync fetches newest release |
fzf@14 |
Latest 14.x | Yes — within major only |
fzf@14.1 |
Latest 14.1.x | Yes — within major.minor only |
fzf@14.1.0 |
Exact version | Never — static pin |
A pinned package keeps the constraint as its name (e.g., fzf@14, not fzf@14.2.1); ghpm list shows the version installed.
ghpm finds the binaries in a release's archive on its own, and puts each on your PATH through ~/.ghpm/bin. GitHub releases are portable apps, so nothing else needs setting up.
You can select multiple assets from a single release, for builds split across assets — e.g. one whose shared libraries ship in a separate archive that must sit beside the executables. Later selections win where files collide.
~/.ghpm/config.toml is not created by default — ghpm runs fine without it. Create it to override any of these defaults:
cache_ttl = "5m"
no_color = false
num_parallel = 5
repo_sources = ["github.com/meop/ghpm-config"]
skip_hash_check = false
[color]
fail = "red"
info = "blue"
new = "cyan"
old = "magenta"
pass = "green"
warn = "yellow"| Field | Default | Description |
|---|---|---|
cache_ttl |
"5m" |
How long cached version data stays fresh before re-fetching |
color |
see above | Output colors by message type |
no_color |
false |
Disable colored output |
num_parallel |
5 |
Max concurrent downloads |
repo_sources |
["github.com/meop/ghpm-config"] |
Remote sources ghpm refresh fetches repo.toml files from |
skip_hash_check |
false |
Permanently skip SHA256 hash verification (same as always passing --skip-hash-check) |
Package names like fzf are resolved to GitHub repos via ~/.ghpm/repo/. Any repo.toml file anywhere in that directory tree contributes to the map — files are merged alphabetically by path, with later files taking precedence on conflicts. Invalid TOML is a fatal error. Each file is one section per package:
[rg]
uri = "github.com/BurntSushi/ripgrep"
descr = "Recursively search directories with regex, respecting gitignore."
aliases = ["ripgrep"]uri is the github.com/owner/repo path. descr is optional — one sentence about the tool, shown as a column by ghpm find and under the header by ghpm info. aliases is optional too — other names the tool is commonly called, accepted wherever the key is: ghpm add ripgrep installs rg. The package is always installed, listed and shimmed under its key. The older flat form (fzf = "github.com/junegunn/fzf") is still read, so an existing personal repo.toml keeps working as is.
A command that acts takes a name whole. add, remove, sync and info accept a package's key or one of its aliases, exactly as spelled (case aside) — rip is not ripgrep, and a shorter spelling you want is another alias in your own repo.toml.
A command that reads takes the start of a word. find, list and outdated match a term against the beginning of any word, a word being whatever sits between characters that are not letters or digits: go finds golangci-lint and go-task, and never cargo. list and outdated look at installed names and their aliases; find also looks at each entry's owner/repo and descr. Look with find, then act with the name it shows.
GitHub itself is only searched as a fallback, and held to the same two rules. For a name the registry does not have, add and info offer the GitHub repos named exactly that, whoever owns them, and you pick one: ghpm add fzf would offer junegunn/fzf and never junegunn/fzf.vim. For a term that found nothing, find offers to search GitHub, and lists the repos whose name or description has a word starting with it. So a vague term goes to find first; add will not guess from it.
ghpm refresh fetches repo.toml files from the sources in repo_sources and writes them into ~/.ghpm/repo/. You can also place your own repo.toml files there in any layout — ~/.ghpm/repo/ is never touched by ghpm tidy and is managed manually (or via ghpm refresh).
If a name isn't in the map, ghpm looks for GitHub repos of exactly that name and prompts you to pick one.
- Each downloaded asset's SHA256 is checked against the digest GitHub reports; a mismatch fails the install (bypass with
--skip-hash-check) synckeeps the choices you made at install time — which binaries to add, what to name them — as long as a release offers the same binaries and fonts; if that set changes, it asks again from scratch- A package that fails partway is rolled back to the version you had, so re-running the command simply retries it; other packages in the same run are unaffected
ghpm upgradereplaces only the ghpm the install script put in~/.ghpm/bin. One installed another way (a package manager,go install) works the same and keeps the same~/.ghpm, but is upgraded by what installed it
A package is unpacked to ~/.ghpm/extract/<name>/<version>, and its shims in ~/.ghpm/bin run it through
~/.ghpm/extract/<name>/version, a file naming that version. An update unpacks the new version beside the old,
rewrites that file, and only then removes the old version, so the shims stay the same files from one version to the
next, and a program still running from the old version (the shell you ran ghpm sync in, say) keeps running. A
version still in use when its turn to go comes is left for ghpm tidy.
Run ghpm upgrade before ghpm sync. A newer ghpm moves each package to the layout above on its next sync, and
ghpm sync --reinstall moves them all at once; the choices you made (which binaries, their names) carry over. Once
it has, do not run an older ghpm on that machine: it looks for packages where they no longer are, and its tidy
would remove them.
Release archives contain LICENSE. Verify the signed checksum manifest
with the public release key before checking downloaded artifacts:
gpg --verify SHA256SUMS.sig SHA256SUMS
sha256sum --ignore-missing --check SHA256SUMS