Fuzzy-pick any GitHub repo you have access to, and
cdstraight into it. Clones it on first use into~/<owner>/<repo>.
ghfzf is a ~220-line bash script that pipes the list of every repo you can
see on GitHub (owner, collaborator, org member — all of them) into
fzf, with a colored picker and a live
preview pane. Its companion fish function ghj takes whichever repo you
select and — if it's not already on disk — clones it via SSH into a
predictable layout, then drops you inside the folder.
The net effect: stop thinking about "is this repo on my machine yet?" Just
type ghj, fuzzy-search, enter.
- Works across every repo you can see. Single paginated GraphQL call
through
gh api; ~1 second for a cached list, ~30-60s for the first fetch against a large-org account (1200+ repos tested). - Predictable checkout layout.
~/<owner>/<repo>by default (e.g.~/acme/api,~/alice/dotfiles). Override with$GHFZF_ROOT. - SSH clones — uses
git@github.com:<owner>/<repo>.gitdirectly, never falls back to HTTPS. - Smart ranking. Repo-name matches rank above description matches (the
basename is the first matchable column in
fzf); fuzzy matching is left on, so typos likeknitsstill findkubernetes. - Color-coded. Repo name in bold cyan, owner in soft gray, language
tinted per-language (GitHub-ish palette), archived repos dimmed so they
recede. Respects
NO_COLOR. - Cached. Uses a local cache; never refreshes it implicitly. Run
ghj fetch(orghfzf --fetch) when you want to update it. - Fish-first. Installer is
install.fish;ghjis a native fish autoloaded function. (The coreghfzftool works from any shell; it's just thecd-the-parent-shell part that's fish-specific.)
gh— authenticated (gh auth login -s repo,read:org -p ssh)gitfzfjqfish— for theghjwrapper and the installer- macOS or Linux
On macOS:
brew install gh fzf jq fish
gh auth login -s repo,read:org -p ssh./install.fishWhat the installer does, all idempotently:
- Verifies the four deps above are on
PATH. - Symlinks
./ghfzfinto~/.local/bin/ghfzfand adds~/.local/bintofish_user_pathsviafish_add_path. - Symlinks
./ghj.fishinto~/.config/fish/functions/so fish autoloads theghjfunction. - Warns if
gh auth statusisn't green.
Symlinks (the default) mean you can edit ghfzf / ghj.fish in this repo
and changes take effect immediately, no reinstall. Pass ./install.fish --copy to install plain copies instead.
Open a new fish shell (or exec fish) and you're ready.
./install.sh --bash # or: --zsh, or bothghj fetch # fetch/update the repo cache (run whenever you need it)
ghj # fuzzy-pick -> cd into the checkout (clones if missing)ghfzf # pick -> ensure checkout -> print absolute path (default)
ghfzf --print-path # same as default, explicit
ghfzf --print # pick -> print "owner/repo", no clone
ghfzf --open # pick -> open in browser
ghfzf --clone # pick -> clone into ~/<owner>/<repo> (and print)
ghfzf --ensure owner/r # non-interactive: ensure checkout, print path
ghfzf --fetch # fetch & write repo cache, then exit
ghfzf -r # force-refresh the repo cache (alias: --refresh)
ghfzf --list # print the cached repo list (plain "owner/repo" per line)
ghfzf --help
ghfzf always prints the result to stdout; progress and diagnostics go to
stderr, so it composes cleanly with other commands.
Enter— ensure checkout and print the path (whatghjconsumes).Ctrl-O— open the selected repo in your browser viagh browse.Ctrl-Y— copy the repo URL to the clipboard (macOSpbcopy).Ctrl-R— force-refresh the repo cache in place.- Prefix a search term with
'for an exact-match-only term (fzf's built-in). Otherwise fuzzy matching is on by default.
~/
├── acme/ # org you belong to
│ ├── api/
│ ├── payments/
│ └── ...
├── alice/ # your personal account
│ ├── dotfiles/
│ └── ...
└── other-org/
└── ...
Set GHFZF_ROOT=/path/to/src to put checkouts under that directory instead
of $HOME. The <owner>/<repo> structure still applies:
$GHFZF_ROOT/<owner>/<repo>.
| Var | Default | Purpose |
|---|---|---|
GHFZF_ROOT |
$HOME |
Parent dir for checkouts; final path is $ROOT/<owner>/<repo> |
NO_COLOR |
unset | Disable all ANSI coloring (follows https://no-color.org) |
XDG_CACHE_HOME |
$HOME/.cache |
Where the repo cache is stored ($XDG_CACHE_HOME/ghfzf/) |
A few implementation details worth knowing if you want to tweak it:
- Columns are emitted by
jqas TSV, padded to fixed widths, then wrapped in ANSI color sequences. Padding happens before coloring so column widths are based on visible character length, not byte length. - Hidden columns at the end of each row carry: the full JSON blob for
the selected repo (fed to the preview pane) and a plain ANSI-free
owner/repo(used by action dispatch, clipboard, and--list). - Basename first for ranking. The first visible column is the bare repo
basename (e.g.
api, notacme/api). fzf's scorer heavily rewards matches that start at position 0 of a matchable field, so queries against the short name reliably outrank description-only matches. Fuzzy matching is still on, so transposition typos work. - Sticky header via
fzf --header-lines=1: the first emitted line isREPO / OWNER / LANG / DESCRIPTIONwith the same padding as the data rows, wrapped in bold+underlined. Ctrl-Rreload delegates back into the script (ghfzf --refresh --rows) rather than inlining jq in a bind spec, to sidestep fzf's comma-splitting of bind action lists.
A child process can't change its parent shell's working directory, so
ghj has to be a function in your shell, not a separate binary. I use
fish, so ghj.fish is what's in the repo. For bash/zsh, this repo ships
ghj.sh (and install.sh wires it into your rc file).
| File | What |
|---|---|
ghfzf |
The main script: fetches, caches, picks, clones, prints paths |
ghj.fish |
Fish function that runs cd "$(ghfzf --print-path $argv)" |
ghj.sh |
bash/zsh function wrapper (source it from your rc file) |
install.fish |
One-shot installer (dep check, symlinks, fish_user_paths) |
install.sh |
One-shot installer for bash/zsh |
README.md |
This file |
"I'm getting stale results."
Run ghj fetch (or ghfzf --fetch). You can also press Ctrl-R in the picker.
"Repository not found" on clone. Three usual causes:
- Your
ghtoken scopes are too narrow — re-rungh auth login -s repo,read:org. - Your SSH key isn't registered with GitHub —
gh ssh-key listshould show it. - You actually can't see the repo (it was deleted, or you were removed from the org since the last cache refresh).
"The picker is slow on the first run."
Expected. The initial ghj fetch / ghfzf --fetch makes one paginated GraphQL
request to fetch every repo you can see. For accounts with 1000+ repos that's
20-60s. All subsequent picks use the local cache and open instantly.
"I want plain output for piping."
NO_COLOR=1 ghfzf --list — one owner/repo per line, no ANSI.
MIT.