Skip to content

Instantly share code, notes, and snippets.

@jdmallen
Created September 7, 2026 03:10
Show Gist options
  • Select an option

  • Save jdmallen/c44b4205d836c53eade4720e964ac6e8 to your computer and use it in GitHub Desktop.

Select an option

Save jdmallen/c44b4205d836c53eade4720e964ac6e8 to your computer and use it in GitHub Desktop.
My optimized .zshrc file, plus a couple files that get loaded from the `~/.zsh/` dir. The `cached_init` stuff I had Claude help me out with to speed up the shell. Works like a charm!
# clipboard
alias pbcopy='xclip -selection clipboard -i'
alias pbfilter='xclip -selection clipboard -f'
alias pbpaste='xclip -selection clipboard -o'
# tool replacements
alias top="htop"
if command -v batcat >/dev/null 2>&1; then
alias bat="batcat"
fi
if command -v plocate >/dev/null 2>&1; then
alias locate="plocate"
fi
alias l="eza"
alias la="eza -hila --time-style='+%y-%m-%d %H:%M:%S'"
alias ll="eza -hil --time-style='+%y-%m-%d %H:%M:%S'"
alias lls="eza -hilars modified --time-style='+%y-%m-%d %H:%M:%S'"
alias clear="clear -x"
# tool shortcuts
alias pn="pnpm"
alias cl="/usr/bin/clear"
alias satt='shot'
alias smassh="linux-smassh"
alias dff="df -hlt ext4"
alias dn="dotnet"
alias upup="topgrade"
alias xo="xdg-open"
alias dt="date +%Y%m%dT%H%M%S%z"
alias dtu="date -u +%Y%m%dT%H%M%SZ"
# ssh shortcuts
alias lh="ssh lh"
alias t14="ssh t14"
alias rpi="ssh rpi"
alias nas="ssh nas"
alias nasroot="ssh nasroot"
# git — only aliases that override OMZ git plugin defaults are kept here.
# Identical-to-OMZ aliases (ga, gco, gcp, gd, gm, gr, gra, grv) and the four
# I opted to drop (gaa, gcl, glp, gp) come from the OMZ git plugin.
alias config="/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME"
alias gap="git add -p" # patch-add (OMZ: git apply)
alias gbr="git branch" # local branches (OMZ: --remote)
alias gc="git commit -S" # signed commit (OMZ: --verbose)
alias gcob="git checkout -b"
alias gcom="git checkout main"
alias gdc="git diff --cached"
alias gdmc="git diff --name-only --diff-filter=\"U\"" # list files with merge conflicts
alias gl="git log --graph --pretty=format:'%C(magenta)%h%C(white) | %C(blue)%an%C(white) | %C(yellow)%ar%C(auto) %(decorate:prefix=| ,suffix=,pointer= ➡️ ,tag=🏷️ )%n%s%n'"
alias glv="git log -p" # patch (verbose)
alias gpl="git pull"
alias grd="git remote remove"
alias grs="git remote set-url" # (OMZ: git restore)
alias gs="git status -sb"
alias gsv="git status"
alias gwho="git shortlog -s --"
#!/usr/bin/env zsh
ghard() {
echo "This will discard all uncommitted changes. Are you sure? (y/n) "
read -k1 -r answer
echo
if [[ "$answer" == [Yy] ]]; then
git reset --hard HEAD
else
echo "Aborted."
fi
}
# deletes uplinks to remote branches that have been deleted
# and deletes local branches that have been merged other than
# master and your current checked out branch
gbc() {
local dry_run=false
# Check for --dry-run flag
if [[ "$1" == "--dry-run" || "$1" == "-n" ]]; then
dry_run=true
echo "Dry-run mode: Showing branches that would be deleted."
fi
echo "Checking for remote branches to prune..."
for remote in $(git remote); do
echo " Pruning $remote..."
if [[ "$dry_run" == true ]]; then
prune_output=$(git remote prune "$remote" --dry-run)
else
prune_output=$(git remote prune "$remote")
fi
if [[ -z "$prune_output" ]]; then
echo " None found on $remote."
else
echo "$prune_output" | sed 's/^/ /g'
fi
done
# Detect main branch name (either "main" or "master")
local MAIN_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')
# Define protected branches
local PROTECTED_BRANCHES=("$MAIN_BRANCH" "main" "master" "dev" "develop" "development" "prod" "production" "staging" "stage" "qa" "uat")
# Get the currently checked out branch
local CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
# Get merged branches, excluding protected and current branches
echo " Checking for merged local branches that would be deleted..."
local deleted_any=false
for b in $(git branch --format='%(refname:short)' --merged | grep -v '^*'); do
if [[ ! " ${PROTECTED_BRANCHES[*]} " =~ " $b " && "$b" != "$CURRENT_BRANCH" ]]; then
deleted_any=true
if [[ "$dry_run" == true ]]; then
echo " Would delete: $b"
else
git branch -d "$b" | sed 's/^/ /g'
fi
fi
done
# If no branches were found to delete, print a message
if [[ "$deleted_any" == false ]]; then
echo " None found."
fi
}
gbra() {
echo " ------------------------------------------------------------"
git for-each-ref --sort="-authordate:iso8601" --format=" %(authordate:relative)%09%(refname:short)" refs/heads
echo " ------------------------------------------------------------"
}
gpush() {
if [[ "$(git config "branch.$(git symbolic-ref --short HEAD).merge")" == "" ]]; then
git push --set-upstream origin "$(git symbolic-ref --short HEAD)"
else
git push
fi
}
_gdirty_usage() {
cat <<-'EOF'
Usage: gdirty [-u] [-n]
Report which immediate subdirectories of $PWD are git repos needing
attention before you switch machines: uncommitted changes, commits not
pushed (^), commits not pulled (v), or a branch with no upstream at all.
-u, --no-untracked ignore untracked files when judging dirtiness
-n, --no-fetch skip the fetch; report against already-known refs
EOF
}
# Report which of $PWD's immediate subdirectories are git repos with
# uncommitted or unsynced work — the "what did I leave behind on this machine?"
# check before switching to the other one. Fetches first (in parallel) so the
# ahead/behind counts reflect what the other machine has actually pushed.
#
# A clean, fully synced repo is omitted. A clean repo on a branch with no
# upstream is still listed, since unpushed work is exactly the thing that turns
# into a conflict later; a clean *detached* HEAD is not, as that is usually just
# a checked-out tag rather than work in progress.
gdirty() {
local untracked=normal
local fetch=true
while [[ "$1" == -* ]]; do
case "$1" in
-u|--no-untracked) untracked=no; shift ;;
-n|--no-fetch) fetch=false; shift ;;
-h|--help) _gdirty_usage; return 0 ;;
*)
echo "gdirty: unknown option: $1" >&2
_gdirty_usage >&2
return 2
;;
esac
done
local dir
local -a repos
for dir in *(N-/); do
# Worktrees and submodules keep .git as a file rather than a directory,
# so test for existence instead of for a directory.
[[ -e "$dir/.git" ]] && repos+=( "$dir" )
done
if (( ${#repos} == 0 )); then
echo "gdirty: no git repos directly under $PWD" >&2
return 1
fi
if [[ "$fetch" == true ]]; then
echo "gdirty: fetching ${#repos} repo(s)..." >&2
# Fetching serially over a dozen-plus repos is painfully slow, so fan
# out and wait. The subshell keeps job-control chatter out of an
# interactive shell. Stdin is closed and prompting disabled so a repo
# wanting credentials fails fast instead of hanging the whole batch
# behind an invisible prompt; timeout does the same for a dead remote.
local -a fetch_timeout=()
(( $+commands[timeout] )) && fetch_timeout=( timeout 20 )
(
for dir in $repos; do
GIT_TERMINAL_PROMPT=0 $fetch_timeout \
git -C "$dir" fetch --quiet --all >/dev/null 2>&1 </dev/null &
done
wait
)
fi
local changes branch sync count
local -a names branches changed syncs lines ab
local width=0 bwidth=0
for dir in $repos; do
changes=$(git -C "$dir" status --porcelain --untracked-files="$untracked" 2>/dev/null)
lines=( ${(f)changes} )
count=${#lines}
branch=$(git -C "$dir" rev-parse --abbrev-ref HEAD 2>/dev/null)
branch="${branch:-?}"
# "A...B" with --left-right --count prints "<left-only>\t<right-only>",
# i.e. behind then ahead for @{upstream}...HEAD.
sync=""
if ab=( ${=$(git -C "$dir" rev-list --left-right --count '@{upstream}...HEAD' 2>/dev/null)} ) \
&& (( ${#ab} == 2 )); then
(( ab[2] > 0 )) && sync+="^${ab[2]} "
(( ab[1] > 0 )) && sync+="v${ab[1]}"
elif [[ "$branch" != HEAD ]]; then
sync="no upstream"
fi
(( count > 0 )) || [[ -n "$sync" ]] || continue
names+=( "$dir" )
branches+=( "$branch" )
changed+=( "$(( count > 0 ? count : 0 ))" )
syncs+=( "${sync% }" )
(( ${#dir} > width )) && width=${#dir}
(( ${#branch} > bwidth )) && bwidth=${#branch}
done
if (( ${#names} == 0 )); then
echo "gdirty: all ${#repos} repo(s) clean and in sync" >&2
return 0
fi
local i
for (( i = 1; i <= ${#names}; i++ )); do
printf '%-*s %-*s %-11s %s\n' \
"$width" "$names[i]" "$bwidth" "$branches[i]" \
"$( (( changed[i] > 0 )) && print -n "${changed[i]} changed" )" \
"$syncs[i]"
done
}
# gitignore generator
# https://www.toptal.com/developers/gitignore/api
_gi_usage() {
cat <<-'EOF'
Usage: gi [-o FILE | --stdout] [-f] <template>[ <template>...]
gi list [term]
Fetch .gitignore templates from gitignore.io. Writes ./.gitignore by default.
gi java maven write java + maven rules to ./.gitignore
gi java,maven,intellij+all commas work too; both forms are equivalent
gi -f java maven overwrite an existing .gitignore
gi --stdout java print instead of writing
gi -o extra.gitignore java write elsewhere (-o beats --stdout)
gi list every template name (570-odd)
gi list intellij only names matching "intellij"
Names are cached in ${XDG_CACHE_HOME:-~/.cache}/gitignore-templates for 7 days.
EOF
}
# Refresh the cached template list if missing or older than a week. Falls back
# to a stale cache when offline rather than failing outright.
_gi_templates() {
local cache="${XDG_CACHE_HOME:-$HOME/.cache}/gitignore-templates"
local -a stale=( ${cache}(N.mh+168) )
[[ -s $cache && -z $stale ]] && return 0
mkdir -p "${cache:h}"
if curl -sfL --max-time 20 \
"https://www.toptal.com/developers/gitignore/api/list?format=lines" \
-o "$cache.tmp" && [[ -s "$cache.tmp" ]]; then
mv "$cache.tmp" "$cache"
return 0
fi
rm -f "$cache.tmp"
if [[ -s $cache ]]; then
echo "gi: could not refresh template list, using cached copy" >&2
return 0
fi
echo "gi: could not fetch template list" >&2
return 1
}
# The API returns HTTP 404 *and* the valid portion of the output when any one
# template name is unknown, so a typo would otherwise write a silently
# incomplete .gitignore. Both the status and the "#!! ERROR" marker are checked.
gi() {
local api="https://www.toptal.com/developers/gitignore/api"
local cache="${XDG_CACHE_HOME:-$HOME/.cache}/gitignore-templates"
local outfile="" force=false to_stdout=false
while [[ "$1" == -* ]]; do
case "$1" in
-o|--output)
if [[ -z "$2" ]]; then
echo "gi: $1 requires a filename" >&2
return 2
fi
outfile="$2"
shift 2
;;
-f|--force) force=true; shift ;;
--stdout) to_stdout=true; shift ;;
-h|--help) _gi_usage; return 0 ;;
--) shift; break ;;
*)
echo "gi: unknown option: $1" >&2
_gi_usage >&2
return 2
;;
esac
done
if (( $# == 0 )); then
_gi_usage >&2
return 2
fi
if [[ "$1" == list ]]; then
_gi_templates || return $?
if [[ -n "$2" ]]; then
grep -i -- "$2" "$cache" && return 0
echo "gi: no template matches '$2'" >&2
return 1
fi
column -c "${COLUMNS:-80}" "$cache"
return 0
fi
# Destination: ./.gitignore by default, --stdout prints instead, and an
# explicit -o takes precedence over both.
local dest="$outfile"
if [[ -z "$dest" && "$to_stdout" == false ]]; then
dest=".gitignore"
fi
if [[ -n "$dest" && -e "$dest" && "$force" == false ]]; then
echo "gi: $dest already exists; pass -f to overwrite, or --stdout to print" >&2
return 1
fi
# `gi java maven` and `gi java,maven` are the same request.
local types="${(j:,:)@}"
# NB: `status` is a read-only special variable in zsh (an alias for $?), so
# the HTTP code cannot be stored in a local of that name.
local response http_code body
if ! response=$(curl -sL --max-time 20 -w '\n%{http_code}' "$api/$types"); then
echo "gi: request to gitignore.io failed" >&2
return 1
fi
http_code="${response##*$'\n'}"
body="${response%$'\n'*}"
if [[ "$http_code" != 200 || "$body" == *'#!! ERROR'* ]]; then
echo "gi: request failed (HTTP $http_code)" >&2
print -r -- "$body" | grep '#!! ERROR' >&2
echo "gi: run 'gi list <term>' to find the correct name" >&2
return 1
fi
if [[ -n "$dest" ]]; then
if ! print -r -- "$body" > "$dest"; then
echo "gi: could not write $dest" >&2
return 1
fi
echo "gi: wrote $(wc -l < "$dest") lines to $dest" >&2
else
print -r -- "$body"
fi
}
# Tab completion. Template names come from the same cache `gi list` populates,
# so completion works offline and costs no network round-trip. `_values -s ,`
# makes comma-separated lists complete element by element: `gi java,mav<TAB>`.
_gi() {
local cache="${XDG_CACHE_HOME:-$HOME/.cache}/gitignore-templates"
local -a templates
local state
_arguments -S \
'(-o --output)'{-o,--output}'[write to FILE instead of ./.gitignore]:file:_files' \
'(-f --force)'{-f,--force}'[overwrite an existing file]' \
'--stdout[print to stdout instead of writing a file]' \
'(-h --help)'{-h,--help}'[show usage]' \
'*:template:->template' && return 0
[[ $state == template ]] || return 1
if [[ -s $cache ]]; then
templates=( ${(f)"$(<$cache)"} )
else
_message 'no cached template list — run `gi list` once'
return 1
fi
# `list` is only meaningful as the first word.
(( CURRENT == 2 )) && templates+=( list )
_values -s , 'gitignore template' $templates
}
if (( $+functions[compdef] )); then
compdef _gi gi
fi
# ~/.zshrc — interactive shell config
# --- Path setup -----------------------------------------------------------
# Dedupe automatically. typeset -U keeps only the first occurrence in $path.
typeset -U path PATH fpath FPATH
# Required env (asdf and brew need their prefixes known before path additions)
export ASDF_DIR="${ASDF_DIR:-$HOME/.asdf}"
export GOPATH="$HOME/go"
export XDG_CONFIG_HOME="$HOME/.config"
export PNPM_HOME="$HOME/.local/share/pnpm"
export CHROME_DEVEL_SANDBOX=/opt/google/chrome/chrome-sandbox
export CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY=1 # Turn off Claude telemetry
export HOMEBREW_NO_ENV_HINTS=1
# ws-lightheat (10.10.0.20) hosts the llama server, so it talks to itself over
# loopback; every other box reaches it across the LAN. $HOST is zsh's builtin
# hostname, so this costs no subprocess; %%.* trims a domain if one is set.
export LLAMA_SERVER_URL=http://10.10.0.20:8080
[[ "${HOST%%.*}" == "ws-lightheat" ]] && export LLAMA_SERVER_URL=http://localhost:8080
# Build path. Optional dirs are gated so this zshrc is portable across machines.
# Order: highest-priority custom dirs first.
path=(
"$ASDF_DIR/shims"
"$ASDF_DIR/bin"
"$PNPM_HOME/bin"
"$HOME/bin"
"$HOME/.local/bin"
"$HOME/.dotnet/tools"
"$HOME/.local/share/JetBrains/Toolbox/scripts"
$path
)
[[ -d "$HOME/Qt/6.11.0/gcc_64/bin" ]] && path=("$HOME/Qt/6.11.0/gcc_64/bin" $path)
[[ -d "/opt/mssql-tools/bin" ]] && path+=("/opt/mssql-tools/bin")
[[ -d "$HOME/.npm-global/bin" ]] && path+=("$HOME/.npm-global/bin")
# Brew adds itself to PATH on first login via /etc/profile.d on most boxes; on
# fresh sessions where HOMEBREW_PREFIX is unset, fall back to shellenv.
if [[ -z $HOMEBREW_PREFIX && -x /home/linuxbrew/.linuxbrew/bin/brew ]]; then
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
fi
# --- Editor ---------------------------------------------------------------
export EDITOR=vim
export VISUAL=vim
# --- Oh My Zsh ------------------------------------------------------------
export ZSH="$HOME/.oh-my-zsh"
ZSH_THEME="jesse" # decent ones: terminalparty, strug, steeef, dst, philips
ZSH_CUSTOM=~/.zsh/
HYPHEN_INSENSITIVE="true"
DISABLE_MAGIC_FUNCTIONS="true"
DISABLE_UNTRACKED_FILES_DIRTY="true"
COMPLETION_WAITING_DOTS="true"
HIST_STAMPS="%FT%H:%M:%S%z"
# Skip OMZ's compaudit security scan (saves ~200-400ms). Trade-off: zsh won't
# warn about world-writable fpath dirs; this box is single-user, so fine.
ZSH_DISABLE_COMPFIX="true"
# We update via topgrade; don't pester us.
zstyle ':omz:update' mode disabled
plugins=(git direnv zsh-autosuggestions zsh-syntax-highlighting)
source "$ZSH/oh-my-zsh.sh"
# --- Tool init (cached) ---------------------------------------------------
# Each `<tool> init zsh` invocation forks the binary (10-150ms cold). Cache the
# generated shell script and source the cache instead. Cache is regenerated
# automatically when the tool binary is newer than the cache file.
_cached_init() {
local name=$1 binary=$2
shift 2
local cache="$HOME/.cache/zsh/${name}.zsh"
local bin_path
bin_path=$(command -v "$binary" 2>/dev/null) || return
if [[ ! -s $cache || $bin_path -nt $cache ]]; then
"$@" > "$cache" 2>/dev/null
fi
source "$cache"
}
# Legacy self-install (cargo-dist) location; atuin now comes from Homebrew. Kept
# guarded for hosts not yet migrated. `if`-form so the un-taken branch returns 0
# and does not dirty $? at the first prompt.
if [[ -d "$HOME/.atuin/bin" ]]; then
path=("$HOME/.atuin/bin" $path)
fi
_cached_init atuin atuin atuin init zsh --disable-up-arrow
_cached_init zoxide zoxide zoxide init zsh
_cached_init fzf fzf fzf --zsh
# `test && source` would propagate the test's exit code when the file is
# absent, leaving $? = 1 on the very first prompt. `if`-form returns 0 from
# the un-taken branch, so the prompt's "previous exit code" starts clean.
if [[ -f $HOME/.openclaw/completions/openclaw.zsh ]]; then
source "$HOME/.openclaw/completions/openclaw.zsh"
fi
# --- Secrets --------------------------------------------------------------
# Load the Anthropic token from a file kept outside this rc so it never lands
# in version control. `read -r` strips the trailing newline; ${VAR:+...} guards
# against exporting an empty value if the file exists but is blank.
if [[ -r $HOME/.anthropic_token ]]; then
read -r ANTHROPIC_API_KEY_SHELLY < "$HOME/.anthropic_token"
[[ -n $ANTHROPIC_API_KEY_SHELLY ]] && export ANTHROPIC_API_KEY_SHELLY
fi
# Azure OpenAI creds for shelly, kept outside this rc for the same reason.
# ~/.azure_creds sets AZURE_OPENAI_API_KEY='...' (no `export`), so we source
# it here and export it ourselves rather than parsing the value directly.
if [[ -r $HOME/.azure_creds ]]; then
source "$HOME/.azure_creds"
[[ -n $AZURE_OPENAI_API_KEY ]] && export AZURE_OPENAI_API_KEY
fi
export AZURE_OPENAI_ENDPOINT_HOST='https://openai-agent-1.openai.azure.com/'
export AZURE_OPENAI_DEPLOYMENT='gpt-4.1-mini'
export AZURE_OPENAI_BASE_URL=$AZURE_OPENAI_ENDPOINT_HOST
#THIS MUST BE AT THE END OF THE FILE FOR SDKMAN TO WORK!!!
export SDKMAN_DIR="$HOME/.sdkman"
[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment