Dotfiles

My zsh + tmux setup. Copy what you need.

~/.zshrc
# Personal Zsh configuration file. It is strongly recommended to keep all
# shell customization and configuration (including exported environment
# variables such as PATH) in this file or in files sourced from it.
#
# Documentation: https://github.com/romkatv/zsh4humans/blob/v5/README.md.

# Periodic auto-update on Zsh startup: 'ask' or 'no'.
# You can manually run `z4h update` to update everything.
zstyle ':z4h:' auto-update      'yes'
# Ask whether to auto-update this often; has no effect if auto-update is 'no'.
zstyle ':z4h:' auto-update-days '28'

# Keyboard type: 'mac' or 'pc'.
zstyle ':z4h:bindkey' keyboard  'mac'

# Start tmux automatically.
zstyle ':z4h:' start-tmux       no

# Mark up shell's output with semantic information.
zstyle ':z4h:' term-shell-integration 'yes'

# Right-arrow key accepts one character ('partial-accept') from
# command autosuggestions or the whole thing ('accept')?
zstyle ':z4h:autosuggestions' forward-char 'partial-accept'

# Recursively traverse directories when TAB-completing files.
zstyle ':z4h:fzf-complete' recurse-dirs 'yes'

# Enable direnv to automatically source .envrc files.
zstyle ':z4h:direnv'         enable 'yes'
# Show "loading" and "unloading" notifications from direnv.
zstyle ':z4h:direnv:success' notify 'yes'

# Enable ('yes') or disable ('no') automatic teleportation of z4h over
# SSH when connecting to these hosts.
# zstyle ':z4h:ssh:example-hostname1'   enable 'yes'
# zstyle ':z4h:ssh:*.example-hostname2' enable 'no'

zstyle ':z4h:fzf-dir-history' height '60%'

# Send these files over to the remote host when connecting over SSH to the
# enabled hosts.
# zstyle ':z4h:ssh:*' send-extra-files '~/.nanorc' '~/.env.zsh'
# The default value if none of the overrides above match the hostname.
zstyle ':z4h:ssh:*'                   enable 'yes'

# Clone additional Git repositories from GitHub.
# z4h install ohmyzsh/ohmyzsh || return
z4h install lukechilds/zsh-nvm || return


# Install or update core components (fzf, zsh-autosuggestions, etc.) and
# initialize Zsh. After this point console I/O is unavailable until Zsh
# is fully initialized. Everything that requires user interaction or can
# perform network I/O must be done above. Everything else is best done below.
z4h init || return

# ----------------------------
# PATH (consolidated)
# ----------------------------
export PYENV_ROOT="$HOME/.pyenv"
export PNPM_HOME="$HOME/Library/pnpm"
path=(
  ~/bin
  /opt/homebrew/opt/postgresql@17/bin
  $PYENV_ROOT/bin(N)
  $PNPM_HOME
  "/Applications/Sublime Text.app/Contents/SharedSupport/bin"
  "/Applications/iTerm.app/Contents/Resources/utilities"
  $HOME/Library/Python/3.9/bin
  $path
)
fpath=($HOME/.docker/completions $fpath)

# ----------------------------
# Environment variables
# ----------------------------
export HOMEBREW_NO_AUTO_UPDATE=1
export HISTSIZE=10000000
export SAVEHIST=10000000
export NVM_LAZY_LOAD=true
export NVM_COMPLETION=true
z4h source $Z4H/lukechilds/zsh-nvm/zsh-nvm.plugin.zsh
export DISABLE_OPENCOLLECTIVE=true
export FZF_DEFAULT_OPTS="--history=$HOME/.fzf-history"
export NEO4J_HOME="$HOME/Developer/neo4j"
export EDITOR='code'
export VISUAL="$EDITOR"
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
export GPG_TTY=$(tty)
export PINENTRY_USER_DATA="USE_CURSES=1"

# Source additional local files if they exist.
source /usr/local/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh

# Use additional Git repositories pulled in with `z4h install`.
#


# Define key bindings.
z4h bindkey undo Ctrl+/   Shift+Tab  # undo the last command line change
z4h bindkey redo Option+/            # redo the last undone command line change
z4h bindkey z4h-fzf-history Ctrl+R   # classic fzf history search over ^R
z4h bindkey z4h-cd-back    Shift+Left   # cd into the previous directory
z4h bindkey z4h-cd-forward Shift+Right  # cd into the next directory
z4h bindkey z4h-cd-up      Shift+Up     # cd into the parent directory
z4h bindkey z4h-cd-down    Shift+Down   # cd into a child directory

# Autoload functions.
autoload -Uz zmv

# Define functions and completions.
function md() { [[ $# == 1 ]] && mkdir -p -- "$1" && cd -- "$1" }
compdef _directories md

function h() {
    # check if we passed any parameters
    if [ -z "$*" ]; then
        # if no parameters were passed print entire history
        history 1
    else
        # if words were passed use it as a search
        history 1 | egrep --color=auto "$@"
    fi
}

ts() {
  command -v tmux &>/dev/null || { echo "tmux not found"; return 1; }
  local sessions=(
    "Blog:~/Developer/tiruma.la"
    "Obsidian:~/Developer/Obsidian"
    "Dev:~/Developer"
    "Home:~"
  )
  local default="${sessions[1]%%:*}" name dir
  for s in "${sessions[@]}"; do
    name="${s%%:*}" dir="${s##*:}"
    tmux has-session -t "$name" 2>/dev/null || \
      tmux new-session -d -s "$name" -c "${dir/#\~/$HOME}"
  done
  tmux switch-client -t "$default" 2>/dev/null || tmux attach -t "$default"
}

# Tmux helper: `t` to pick a session, `t name` to attach-or-create
t() {
  command -v tmux &>/dev/null || { echo "tmux not found"; return 1; }
  if [[ -n "$1" ]]; then
    tmux new-session -A -s "$1"
    return
  fi
  local sessions
  sessions=$(tmux list-sessions -F '#{session_name}' 2>/dev/null)
  if [[ -z "$sessions" ]]; then
    tmux new-session
  else
    local session
    session=$(echo "$sessions" | fzf --reverse --header='Attach to session (or Esc for new)')
    if [[ -n "$session" ]]; then
      if [[ -n "$TMUX" ]]; then
        tmux switch-client -t "$session"
      else
        tmux attach -t "$session"
      fi
    else
      tmux new-session
    fi
  fi
}

# Set shell options: http://zsh.sourceforge.net/Doc/Release/Options.html.
setopt glob_dots     # no special treatment for file names with a leading dot
setopt no_auto_menu  # require an extra TAB press to open the completion menu
setopt EXTENDED_HISTORY
setopt INC_APPEND_HISTORY
setopt SHARE_HISTORY
setopt HIST_VERIFY
setopt HIST_IGNORE_SPACE      # prefix command with space to exclude from history
setopt HIST_REDUCE_BLANKS     # trim extra whitespace
setopt HIST_IGNORE_ALL_DUPS   # remove older duplicate when new entry is added
setopt CORRECT
setopt AUTO_CD
setopt INTERACTIVE_COMMENTS
setopt HIST_FIND_NO_DUPS


# Define aliases.
alias openshell="code $HOME/.zshrc"
alias cls='clear'
alias ll="eza -alh"
alias ls="eza"
alias lt="eza --tree --level=2"
alias tree='tree -a -I .git'



alias dcl="docker compose logs -f"
alias dcd="docker compose down --remove-orphans; docker image prune -f"
alias drv="docker compose down --remove-orphans -v; docker image prune -f"
alias dcu="docker compose up -d"
alias dcb="docker compose build"
alias dni="docker network inspect"
alias dps="docker ps -a"
alias dbp="docker builder prune -a"

alias tpl="tofu plan"
alias tva="tofu validate"
alias tapp="tofu apply"
alias tfmt="tofu fmt -recursive ."
alias tfi="tofu init"

alias trs="tmux kill-server"

eval "$(pyenv init - zsh)"
eval "$(uv generate-shell-completion zsh)"
eval "$(zoxide init zsh)"
test -e "${HOME}/.iterm2_shell_integration.zsh" && source "${HOME}/.iterm2_shell_integration.zsh"

# Auto-start tmux dev session
# if command -v tmux &>/dev/null && [ -z "$TMUX" ] && [ -t 0 ]; then
#   ts
# fi
~/.tmux.conf
# ==============================================================================
# TMUX CONFIG
# ==============================================================================

# ----------------------------
# Prefix: C-a (easier than C-b)
# ----------------------------
unbind C-b
set -g prefix C-a
bind C-a send-prefix
# ----------------------------
# General
# ----------------------------
set -sg escape-time 0                    # no delay for escape (critical for vim)
set -g history-limit 100000              # big scrollback for log tailing
set -g display-time 2000               # show messages longer
set -g status-interval 5               # refresh status bar every 5s
set -g mouse on                        # mouse for scroll, select, resize
set -g base-index 1                    # windows start at 1
set -g pane-base-index 1              # panes start at 1
set -g renumber-windows on             # re-number on close
setw -g automatic-rename on             # auto-rename to running command
set -g automatic-rename-format '#{b:pane_current_path}'
set -g focus-events on                 # for vim autoread etc
set -g set-clipboard on                # OSC 52 clipboard
setw -g mode-keys vi                   # vi keys in copy mode
set -g detach-on-destroy off           # switch to another session instead of detaching
set -g default-shell /bin/zsh

# ----------------------------
# Terminal / True Color (iTerm2)
# ----------------------------
set -g default-terminal "tmux-256color"
set -ga terminal-overrides ",xterm-256color*:Tc"
set -as terminal-overrides ',*:Smulx=\E[4::%p1%dm'
set -as terminal-overrides ',*:Setulc=\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m'

# iTerm2 title integration
set -g set-titles on
set -g set-titles-string '#S → #W'

# ----------------------------
# Splits & Windows (keep cwd)
# ----------------------------
bind | split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
bind c new-window -c "#{pane_current_path}"
unbind '"'
unbind %

# ----------------------------
# Pane Navigation (vim-style)
# ----------------------------
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R

# Alt+hjkl — switch panes without prefix
bind -n M-h select-pane -L
bind -n M-l select-pane -R
bind -n M-j select-pane -D
bind -n M-k select-pane -U

# Resize with prefix + H/J/K/L (repeatable)
bind -r H resize-pane -L 5
bind -r J resize-pane -D 5
bind -r K resize-pane -U 5
bind -r L resize-pane -R 5

# Alt+, / Alt+. — prev/next window without prefix
bind -n M-, previous-window
bind -n M-. next-window

# Tab — toggle last window
bind Tab last-window

# Swap windows left/right
bind -r < swap-window -t -1\; select-window -t -1
bind -r > swap-window -t +1\; select-window -t +1
bind -r C-h previous-window
bind -r C-l next-window

# ----------------------------
# Copy Mode (vi — tmux-yank handles clipboard integration)
# ----------------------------
bind -T copy-mode-vi v   send-keys -X begin-selection
bind -T copy-mode-vi C-v send-keys -X rectangle-toggle

# ----------------------------
# Utility Keybindings
# ----------------------------
bind r source-file ~/.tmux.conf \; display-message "  Config reloaded!"

# Popup windows (tmux 3.2+)
bind g display-popup -E -xC -yC -w 80% -h 80% -d "#{pane_current_path}" lazygit
bind T display-popup -E -xC -yC -w 80% -h 80% -d "#{pane_current_path}"

# Session fuzzy finder via fzf
bind s display-popup -E "\
  tmux list-sessions -F '#{?session_attached,*, } #{session_name}' \
  | fzf --reverse --header='Switch Session' \
  | sed 's/^[* ]*//' \
  | xargs tmux switch-client -t"

# Window fuzzy finder
bind w display-popup -E "\
  tmux list-windows -a -F '#S:#I #W' \
  | fzf --reverse --header='Switch Window' \
  | awk '{print \$1}' \
  | xargs tmux switch-client -t"

# Quick pane toggles
bind m resize-pane -Z   # zoom/unzoom current pane (maximize)
bind x confirm-before -p "Kill pane? (y/n)" kill-pane
bind X confirm-before -p "Kill window? (y/n)" kill-window
bind Q confirm-before -p "Kill session '#S'? (y/n)" kill-session

# Quick session creation with a name prompt
bind N command-prompt -p "New session:" "new-session -s '%%'"

# Rename pane title
bind t command-prompt -p "Pane title:" "select-pane -T '%%'"

# Break pane into its own window
bind B break-pane

# Join a pane from another window (inverse of break)
bind M-j command-prompt -p "Join pane from:" "join-pane -s '%%'"

# Cheatsheet popup (C-a ?)
bind ? display-popup -E -w 40% -h 80% "~/.tmux/scripts/cheatsheet.sh"

# ----------------------------
# Claude Code Integration
# ----------------------------

# C-a y — Capture visible pane content to clipboard
bind y run-shell "~/.tmux/scripts/capture-pane.sh visible"

# C-a Y — Capture last command's output to clipboard
bind Y run-shell "~/.tmux/scripts/capture-pane.sh last-cmd"

# C-a C-y — Capture the OTHER pane's visible content (grab logs/errors while in Claude)
bind C-y run-shell "~/.tmux/scripts/capture-other-pane.sh"

# C-a S — Capture full scrollback to clipboard
bind S run-shell "~/.tmux/scripts/capture-pane.sh all"

# ----------------------------
# Status Bar — Amber (shadcn Lyra)
# ----------------------------
# Palette:
#   bg:      #0a0a0a   card:    #171717   surface: #161616
#   sec:     #262626   accent:  #404040   muted:   #a1a1a1
#   fg:      #fafafa   amber50: #fffbeb
#   amber3:  #ffd236   amber4:  #fcbb00   amber5:  #f99c00
#   amber6:  #dd7400   amber7:  #b75000   amber8:  #953d00
#   amber9:  #7b3306   amber95: #461901   red:     #ff6568

set -g status-position top
set -g status-justify left
set -g status-style "bg=#171717,fg=#a1a1a1"

# Left: empty (windows render here via status-justify left)
set -g status-left-length 0
set -g status-left ""

# Right: indicators + session name
set -g status-right-length 80
set -g status-right "\
#{?client_prefix,#[fg=#ffd236]⌨ ,}\
#{?pane_synchronized,#[fg=#ff6568]⚏ ,}\
#[fg=#f99c00,bg=#171717]\
#[fg=#171717,bg=#f99c00,bold]  #S "

# Window tabs — inactive
setw -g window-status-format "\
#[fg=#171717,bg=#0a0a0a]\
#[fg=#a1a1a1,bg=#0a0a0a] #I  #W \
#[fg=#0a0a0a,bg=#171717]"

# Window tabs — active (amber glow)
setw -g window-status-current-format "\
#[fg=#171717,bg=#dd7400]\
#[fg=#fffbeb,bg=#dd7400,bold] #I  #W \
#[fg=#dd7400,bg=#171717]"

setw -g window-status-separator ""

# Pane borders
set -g pane-border-style "fg=#262626"
set -g pane-active-border-style "fg=#f99c00"

# Pane title bar (shows on top border)
set -g pane-border-status top
set -g pane-border-format " #{?pane_active,#[fg=#fcbb00],#[fg=#953d00]}#T "

# Dim inactive panes, full brightness for active
set -g window-style "fg=#a1a1a1"
set -g window-active-style "fg=#fafafa"

# Message / command prompt styling
set -g message-style "bg=#262626,fg=#ffd236"
set -g message-command-style "bg=#262626,fg=#f99c00"

# Copy mode / selection highlight
# A) Warm brown + gold (amber-900 / amber-300)
# setw -g mode-style "bg=#7b3306,fg=#ffd236"
# B) Neutral gray + amber text (accent / amber-500)
setw -g mode-style "bg=#404040,fg=#f99c00"
# C) Subtle amber tint + white (dark amber / foreground)
# setw -g mode-style "bg=#453519,fg=#fafafa"
# D) Minimal + gold text (secondary / amber-300)
# setw -g mode-style "bg=#262626,fg=#ffd236"

# Clock mode
set -g clock-mode-colour "#f99c00"

# Popup border (tmux 3.3+)
set -g popup-border-style "fg=#404040"

# ----------------------------
# Plugins (managed by TPM)
# ----------------------------
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-yank'
set -g @plugin 'tmux-plugins/tmux-logging'
set -g @plugin 'akohlbecker/aw-watcher-tmux'
set -g @plugin 'jaclu/tmux-menus'
set -g @plugin 'tmux-plugins/tmux-sensible'
set -g @plugin 'sainnhe/tmux-fzf'
set -g @plugin 'Morantron/tmux-fingers'

# Logging plugin: prefix + shift+p to toggle logging a pane to file
# prefix + alt+p to save full pane history
# Logs go to ~/tmux-logs/
set -g @logging-path "$HOME/tmux-logs"

# Initialize TPM (MUST be last line)
run '~/.tmux/plugins/tpm/tpm'
~/.tmux/scripts/capture-pane.sh
#!/bin/bash
# Capture visible pane content to clipboard
# Usage: capture-pane.sh [mode]
#   visible  - capture what's currently visible on screen (default)
#   last-cmd - capture output of the last command (between last two prompts)
#   all      - capture entire scrollback

mode="${1:-visible}"
pane_id="${2:-}"

case "$mode" in
  visible)
    tmux capture-pane ${pane_id:+-t "$pane_id"} -p | pbcopy
    tmux display-message " Visible pane content copied to clipboard"
    ;;
  last-cmd)
    # Capture full scrollback, then extract last command output
    # Looks for the last two shell prompts and grabs content between them
    tmux capture-pane ${pane_id:+-t "$pane_id"} -p -S -500 | \
      awk '
        /^[^ \t].*[\$#%>] / || /^❯ / || /^➜ / || /^\$ / {
          if (buf != "") prev = buf
          buf = ""
          collecting = 1
          next
        }
        collecting { buf = buf $0 "\n" }
        END { printf "%s", buf }
      ' | pbcopy
    tmux display-message " Last command output copied to clipboard"
    ;;
  all)
    tmux capture-pane ${pane_id:+-t "$pane_id"} -p -S - | pbcopy
    tmux display-message " Full scrollback copied to clipboard"
    ;;
esac
~/.tmux/scripts/capture-other-pane.sh
#!/bin/bash
# Capture content from the "other" pane (not the active one) to clipboard
# Useful when Claude is in one pane and you want to grab output from the other

current=$(tmux display-message -p '#{pane_id}')
# Get the last active pane that isn't this one
other=$(tmux list-panes -F '#{pane_id}' | grep -v "^${current}$" | head -1)

if [ -z "$other" ]; then
  tmux display-message " No other pane to capture from"
  exit 1
fi

tmux capture-pane -t "$other" -p | pbcopy
tmux display-message " Other pane content copied to clipboard"
~/.tmux/scripts/cheatsheet.sh
#!/bin/bash

# Colors
b=$'\033[1m'      # bold
d=$'\033[2m'      # dim
a=$'\033[33m'     # amber
r=$'\033[0m'      # reset
h="${b}${a}"      # heading

cat <<EOF
  ${h}TMUX CHEATSHEET${r}

  ${h}PANES${r}
  C-a |         Split side by side
  C-a -         Split top/bottom
  Alt h j k l   Move between panes ${d}(no prefix)${r}
  C-a h j k l   Move between panes
  C-a H J K L   Resize pane ${d}(repeatable)${r}
  C-a x         Kill pane
  C-a T         Shell

  ${h}WINDOWS${r}
  C-a c         New window
  Alt , / .     Prev / next window ${d}(no prefix)${r}
  C-a Tab       Toggle last window
  C-a < / >     Swap window left / right ${d}(repeatable)${r}
  C-a X         Kill window

  ${h}SESSIONS${r}
  C-a N         New named session
  C-a s         Session picker ${d}(fzf)${r}
  C-a w         Window picker ${d}(fzf)${r}
  C-a Q         Kill session

  ${h}COPY TO CLIPBOARD${r}
  ${b}Quick capture${r}
  C-a y         Copy visible pane
  C-a Y         Copy last command output
  C-a C-y       Copy the other pane
  C-a S         Copy full scrollback

  ${b}Select & copy${r}  ${d}(enter copy mode first)${r}
  C-a [         Enter copy mode ${d}(scroll & select)${r}
  v             Start selection
  C-v           Toggle rectangle select
  y             Yank to clipboard
EOF
read -rsn1