My zsh Terminal Setup for Faster Development
Ten small tools and one shell hook that save me time every day, including switching GitHub accounts by folder.
I spend most of my working day in a terminal, so every small annoyance adds up. This is the setup I run on my Mac: 53 lines of .zshrc, ten tools from Homebrew, and a shell that still starts in about a tenth of a second.
TL;DR
- Three zsh plugins give you suggestions as you type, colours for typos, and history search with the arrow keys.
fzf,zoxideandstarshipmake finding files, jumping between folders and reading the prompt fast.ezaandbatreplacelsandcatwith versions that are easier to read.- A million lines of shared history, with timestamps, so nothing you typed last month is lost.
- A small hook switches my GitHub account automatically depending on which folder I'm in.
The problem
Out of the box, zsh on macOS does very little:
- You retype long commands because nothing suggests them.
- A typo in a command only shows up after you press Enter.
- The Up arrow walks through every command, not the ones that start with what you typed.
- History is saved when a tab closes, so two open tabs don't see each other's commands.
cdinto a project means typing or tab-completing the full path every time.
And one problem that cost me real time: I have two GitHub accounts, one for my own work and one for a side project. The GitHub CLI only has one active account at a time, so I kept opening pull requests from the wrong one.
Why it happened
zsh is a toolbox, not a finished product. Its defaults are minimal on purpose and expect you to add what you need. History is a good example: the default size is small, and it's written when the shell exits, not after each command.
The GitHub problem is similar. gh remembers several accounts, but it has one global "active" account. It has no idea that a folder belongs to a different one, so you have to remember to switch. I didn't always remember.
How I fixed it
Instead of a big framework like Oh My Zsh, I install a handful of focused tools with Homebrew and wire them up myself. Each one does one job, the whole config fits on one screen, and startup stays fast because nothing loads that I don't use.
For GitHub, I let the shell do the remembering. zsh can run a function every time you change directory, so a short hook checks where I am and hands gh the right account's token.
Steps
Install everything with Homebrew.
brew install zsh-completions zsh-autosuggestions zsh-syntax-highlighting \ zsh-history-substring-search fzf zoxide starship eza bat ghLoad completions and suggestions. Add this to
~/.zshrc:BREW_PREFIX=$(brew --prefix) # ask Homebrew once, not once per plugin FPATH=$BREW_PREFIX/share/zsh-completions:$FPATH autoload -Uz compinit && compinit source $BREW_PREFIX/share/zsh-autosuggestions/zsh-autosuggestions.zshOpen a new tab and start typing a command you've used before. A grey suggestion appears; press the Right arrow to accept it.
Add fuzzy search, smart
cdand the prompt, then the two order-sensitive plugins at the end.source <(fzf --zsh) eval "$(zoxide init zsh)" eval "$(starship init zsh)" # These two go last, in this order source $BREW_PREFIX/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh source $BREW_PREFIX/share/zsh-history-substring-search/zsh-history-substring-search.zsh bindkey '^[[A' history-substring-search-up bindkey '^[[B' history-substring-search-downCtrl+Ropens a fuzzy search over your history,Ctrl+Tover files. (fzf --zshneeds fzf 0.48 or newer.)- After visiting a folder once,
z portfoliojumps straight back to it from anywhere. - Starship shows the Git branch, status and language versions in the prompt, with no config needed.
- Type
gitand press Up: you only see yourgitcommands.
The order of those two matters. When zsh-history-substring-search loads, it checks whether syntax highlighting is already there and hooks into it, so its README asks for syntax highlighting first. zsh-syntax-highlighting's own README asks to come after anything that adds key bindings: on older versions it only covers shortcuts that exist when it loads. On zsh 5.9 with plugin version 0.8 it hooks every redraw instead, so that part matters less, but the order above suits both.
Swap in friendlier
lsandcat.alias ls="eza --icons --group-directories-first" alias ll="eza -la --icons --git --group-directories-first" alias tree="eza --tree --icons --level=2 --ignore-glob='node_modules|.git'" alias cat="bat --paging=never"llshows each file's Git status next to it, andcatnow highlights syntax.Keep all your history, shared across tabs.
HISTFILE=~/.zsh_history HISTSIZE=1000000 SAVEHIST=1000000 setopt EXTENDED_HISTORY # save when each command ran and how long it took setopt SHARE_HISTORY # every tab sees every other tab's commands setopt HIST_IGNORE_SPACE # start a command with a space to keep it out of history setopt HIST_VERIFY # show what !! expands to before running it setopt HIST_REDUCE_BLANKS setopt HIST_IGNORE_DUPS alias h="history -i 1" # full history with timestamps alias hg="history -i 1 | grep" # e.g. hg dockerHIST_IGNORE_SPACEis the one to remember: if you ever have to type a password or token on the command line, put a space in front and it's never written to disk.Switch GitHub accounts by folder. Log in to both accounts once with
gh auth login. Then add:_gh_account_by_dir() { if [[ "$PWD/" == "$HOME/work/side-project/"* ]]; then if [[ -z "$_GH_SIDE" ]]; then local token token="$(gh auth token --hostname github.com --user second-account 2>/dev/null)" if [[ -n "$token" ]]; then export GH_TOKEN="$token" _GH_SIDE=1 else print -u2 "gh: second-account isn't logged in here. Run: gh auth login" fi fi elif [[ -n "$_GH_SIDE" ]]; then unset GH_TOKEN _GH_SIDE fi } autoload -Uz add-zsh-hook add-zsh-hook chpwd _gh_account_by_dir _gh_account_by_dirChange
side-projectandsecond-accountto your own folder and account. The token isn't stored in.zshrc: it's read fromgh's own secure storage when you enter the folder. The last line runs the check once at startup, so a new tab opened inside the folder is right too.Two details make it safe. If the second account isn't logged in, the hook says so instead of quietly carrying on with your main account, and it tries again on the next
cd. And the trailing/in"$PWD/"means a folder that only starts with the same name, likeside-project-old, doesn't count.Check it worked:
cd ~/work/side-project && gh api user --jq .login # second-account cd ~ && gh api user --jq .login # your main account
How it works
zsh fires a chpwd event every time the current directory changes. The hook only does work when you cross into or out of the side-project folder; moving around inside it costs nothing.
sequenceDiagram
participant You
participant zsh
participant Hook as _gh_account_by_dir
participant gh as GitHub CLI
You->>zsh: cd ~/work/side-project/api
zsh->>Hook: chpwd event
Hook->>Hook: inside side-project, not switched yet
Hook->>gh: gh auth token --user second-account
gh-->>Hook: token from secure storage
Hook->>zsh: export GH_TOKEN
You->>gh: gh pr create
gh-->>You: PR opened as second-account
You->>zsh: cd ~/work/portfolio
zsh->>Hook: chpwd event
Hook->>zsh: unset GH_TOKEN
You->>gh: gh pr create
gh-->>You: PR opened as your main account
gh always prefers GH_TOKEN over its active account when the variable is set, which is what makes this work without ever running gh auth switch.
How to avoid it next time
- Comment every line. Mine are commented in place, so six months later I still know why each option is there.
- Watch startup time. Run
time zsh -i -c exitafter adding anything. Mine takes about 0.1 seconds; if yours creeps past half a second, something you added is slow. - Keep secrets out of dotfiles. Read tokens from a secure store, like the
gh auth tokencall above, instead of pasting them into.zshrc. - Let the shell remember context for you. If you keep forgetting to switch something per project, a
chpwdhook is often a ten-line fix.
Tools used
- Homebrew: installs everything below
- zsh-autosuggestions: grey suggestions from your history as you type
- zsh-syntax-highlighting: colours commands so typos stand out before you press Enter (load it near the end)
- zsh-history-substring-search: Up and Down search history for what you've typed so far (load it right after syntax highlighting)
- zsh-completions: extra Tab completions for common tools
- fzf: fuzzy search for history (
Ctrl+R) and files (Ctrl+T) - zoxide: jump to folders you've visited with
z name - Starship: a fast prompt showing Git and language info
- eza:
lswith icons, colours and Git status - bat:
catwith syntax highlighting - GitHub CLI:
gh, which the account-switching hook drives
Comments
Comments and votes open again when the site is back online.