Skip to content

Instantly share code, notes, and snippets.

@d0zingcat
Forked from eherrerosj/ghostty-config
Last active March 31, 2026 11:00
Show Gist options
  • Select an option

  • Save d0zingcat/df58a8aef876df7586bb561043545921 to your computer and use it in GitHub Desktop.

Select an option

Save d0zingcat/df58a8aef876df7586bb561043545921 to your computer and use it in GitHub Desktop.
Ghostty terminal config + Claude Code worktree hook (2x2 layout: Claude Code, lazygit, terminal, yazi)
# vim: set filetype=toml :
# ============================================
# Ghostty Terminal Configuration
# ============================================
# File: ~/.config/ghostty/config
# Reload: Cmd+Shift+,
# View options: ghostty +show-config --default --docs
# --- Typography ---
font-family = "JetBrainsMono Nerd Font Mono"
font-size = 13
font-thicken = true
adjust-cell-height = 2
# --- Theme and Colors ---
theme = Catppuccin Mocha
# --- Window and Appearance ---
background-opacity = 0.9
background-blur-radius = 20
macos-titlebar-style = transparent
window-padding-x = 10
window-padding-y = 8
window-save-state = always
window-theme = auto
# --- Cursor ---
cursor-style = bar
cursor-style-blink = true
cursor-opacity = 0.8
# --- Mouse ---
mouse-hide-while-typing = true
copy-on-select = clipboard
# --- Quick Terminal (Quake-style dropdown) ---
quick-terminal-position = top
quick-terminal-screen = mouse
quick-terminal-autohide = true
quick-terminal-animation-duration = 0.15
# --- Security ---
clipboard-paste-protection = true
clipboard-paste-bracketed-safe = true
# --- Shell Integration ---
shell-integration = detect
# --- Keybindings ---
keybind = cmd+t=new_tab
keybind = cmd+alt+h=previous_tab
keybind = cmd+alt+l=next_tab
keybind = cmd+w=close_surface
keybind = cmd+d=new_split:right
keybind = cmd+shift+d=new_split:down
keybind = cmd+shift+h=goto_split:left
keybind = cmd+shift+j=goto_split:bottom
keybind = cmd+shift+k=goto_split:top
keybind = cmd+shift+l=goto_split:right
keybind = global:ctrl+grave_accent=toggle_quick_terminal
keybind = cmd+shift+e=equalize_splits
keybind = cmd+shift+f=toggle_split_zoom
keybind = cmd+shift+comma=reload_config
keybind = cmd+plus=increase_font_size:1
keybind = cmd+minus=decrease_font_size:1
keybind = cmd+zero=reset_font_size
# --- Performance ---
scrollback-limit = 25000000
confirm-close-surface = false
#!/bin/zsh
# Hook: Worktree Ghostty Layout
#
# Creates git worktrees in a sibling directory and opens a Ghostty terminal
# in a 2x2 layout: Claude Code (TL), lazygit (TR), terminal (BL), yazi (BR).
# Prompts for a custom branch name via macOS dialog; leave empty for auto-generated.
#
# Events:
# - WorktreeCreate: Creates worktree + opens Ghostty 2x2 layout
# - WorktreeRemove: Removes worktree, branch, and empty directories
#
# Requirements:
# - jq (JSON parsing)
# - Ghostty terminal (macOS)
# - lazygit (git TUI)
# - yazi (file manager TUI)
#
# Location: ~/.claude/hooks/worktree-ghostty-layout.sh
#
# Installation (add to ~/.claude/settings.json):
# "hooks": {
# "WorktreeCreate": [{ "hooks": [{ "type": "command", "command": "/Users/d0zingcat/.claude/hooks/worktree-ghostty-layout.sh" }] }],
# "WorktreeRemove": [{ "hooks": [{ "type": "command", "command": "/Users/d0zingcat/.claude/hooks/worktree-ghostty-layout.sh" }] }]
# }
# Note: use absolute paths only — no ~ expansion in settings.json hooks.
#
# Usage:
# 1. Open Claude Code in any git repository directory
# 2. Ask Claude to create a worktree, e.g.: "work in a worktree" or "start a worktree"
# 3. Claude calls EnterWorktree → WorktreeCreate hook fires automatically
# 4. The hook creates the worktree and opens a Ghostty 2x2 split layout
# To remove: ask Claude to "exit the worktree" → WorktreeRemove hook fires
#
# Ghostty keybindings required:
# super+d = new_split:right
# super+shift+d = new_split:down
# super+shift+h = goto_split:left
INPUT=$(cat)
if ! command -v jq &>/dev/null; then
echo "jq is required but not installed" >&2
exit 1
fi
HOOK_EVENT=$(echo "$INPUT" | jq -r '.hook_event_name')
CWD=$(echo "$INPUT" | jq -r '.cwd')
#-----------------------------------------------------------------------
# WorktreeCreate: Create worktree in sibling dir + open Ghostty layout
#-----------------------------------------------------------------------
create_worktree() {
local NAME
NAME=$(echo "$INPUT" | jq -r '.name')
# Prompt for branch name via macOS dialog (can't use /dev/tty - Claude Code owns the terminal)
local CUSTOM_BRANCH=""
CUSTOM_BRANCH=$(osascript -e 'display dialog "Branch name (leave empty for auto-generated):" default answer "" buttons {"Cancel", "OK"} default button "OK"' -e 'text returned of result' 2>/dev/null) || CUSTOM_BRANCH=""
local REPO_NAME PARENT_DIR WORKTREE_DIR BRANCH_NAME
REPO_NAME=$(basename "$CWD")
PARENT_DIR=$(cd "$CWD/.." && pwd)
WORKTREE_DIR="$PARENT_DIR/worktrees/$REPO_NAME/$NAME"
if [ -n "$CUSTOM_BRANCH" ]; then
BRANCH_NAME="$CUSTOM_BRANCH"
else
BRANCH_NAME="worktree-$NAME"
fi
# Detect default remote branch
local DEFAULT_BRANCH
DEFAULT_BRANCH=$(cd "$CWD" && git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@')
: "${DEFAULT_BRANCH:=main}"
# Create git worktree in sibling directory
mkdir -p "$PARENT_DIR/worktrees/$REPO_NAME" >&2
cd "$CWD" || exit 1
git fetch origin &>/dev/null || true
local BASE_REF="origin/$DEFAULT_BRANCH"
if ! git rev-parse "$BASE_REF" &>/dev/null; then
BASE_REF="HEAD"
fi
git worktree add -b "$BRANCH_NAME" "$WORKTREE_DIR" "$BASE_REF" >&2 || {
echo "Failed to create worktree: $NAME" >&2
exit 1
}
# Open Ghostty 2x2 layout:
# +---------------+---------------+
# | Claude Code | lazygit |
# | (top-left) | (top-right) |
# +---------------+---------------+
# | Terminal | yazi |
# | (bottom-left) | (bottom-right)|
# +---------------+---------------+
# Uses clipboard + Cmd+V for reliable text input (keystroke is unreliable for long paths)
{
sleep 1.5
osascript <<APPLESCRIPT >/dev/null 2>&1
-- Save current clipboard
try
set oldClip to the clipboard as text
on error
set oldClip to ""
end try
tell application "System Events"
tell process "Ghostty"
-- 1. Split right (Cmd+D) -> focus moves to right pane
keystroke "d" using {command down}
delay 1.0
-- 2. cd + lazygit in right pane (top-right)
set the clipboard to "cd '${WORKTREE_DIR}' && lazygit"
keystroke "v" using {command down}
delay 0.3
key code 36
delay 2.0
-- 3. Split down (Cmd+Shift+D) -> focus moves to bottom-right
keystroke "d" using {command down, shift down}
delay 1.0
-- 4. cd + yazi in bottom-right
set the clipboard to "cd '${WORKTREE_DIR}' && yazi"
keystroke "v" using {command down}
delay 0.3
key code 36
delay 0.5
-- 5. Navigate to Claude Code (Cmd+Shift+H)
keystroke "h" using {command down, shift down}
delay 0.5
-- 6. Split down (Cmd+Shift+D) -> focus moves to bottom-left
keystroke "d" using {command down, shift down}
delay 1.0
-- 7. cd into worktree (terminal pane, bottom-left)
set the clipboard to "cd '${WORKTREE_DIR}'"
keystroke "v" using {command down}
delay 0.3
key code 36
end tell
end tell
-- Restore clipboard
delay 0.5
set the clipboard to oldClip
APPLESCRIPT
} &>/dev/null &
# Output the worktree path (the ONLY stdout Claude Code reads)
echo "$WORKTREE_DIR"
}
#-----------------------------------------------------------------------
# WorktreeRemove: Clean up worktree, branch, and empty directories
#-----------------------------------------------------------------------
remove_worktree() {
local WORKTREE_PATH
WORKTREE_PATH=$(echo "$INPUT" | jq -r '.worktree_path')
[ ! -d "$WORKTREE_PATH" ] && exit 0
# Find main repo (first entry in worktree list)
local MAIN_REPO BRANCH_NAME
MAIN_REPO=$(git -C "$WORKTREE_PATH" worktree list --porcelain 2>/dev/null | head -1 | sed 's/^worktree //')
BRANCH_NAME="worktree-$(basename "$WORKTREE_PATH")"
# Remove worktree and branch
cd "$MAIN_REPO" 2>/dev/null || exit 0
git worktree remove "$WORKTREE_PATH" --force 2>/dev/null || rm -rf "$WORKTREE_PATH"
git branch -D "$BRANCH_NAME" 2>/dev/null
# Clean up empty parent directories
rmdir "$(dirname "$WORKTREE_PATH")" 2>/dev/null
}
#-----------------------------------------------------------------------
# Event dispatcher
#-----------------------------------------------------------------------
case "$HOOK_EVENT" in
WorktreeCreate) create_worktree ;;
WorktreeRemove) remove_worktree ;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment