Last active
March 31, 2026 11:13
-
-
Save eherrerosj/cf10dd15d12f55874be53264aa885ce8 to your computer and use it in GitHub Desktop.
Ghostty terminal config + Claude Code worktree hook (2x2 layout: Claude Code, lazygit, terminal, yazi)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ============================================ | |
| # Ghostty Terminal - Complete Configuration | |
| # ============================================ | |
| # File: ~/.config/ghostty/config | |
| # Reload: Cmd+Shift+, (macOS) | |
| # View options: ghostty +show-config --default --docs | |
| # --- Typography --- | |
| font-family = JetBrainsMonoNerdFont | |
| font-size = 12 | |
| font-thicken = true | |
| adjust-cell-height = 2 | |
| # --- Theme and Colors --- | |
| # Catppuccin with automatic light/dark switching | |
| 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) --- | |
| # Activate with the global keybind configured below | |
| 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 --- | |
| # Tabs | |
| keybind = cmd+t=new_tab | |
| keybind = cmd+shift+left=previous_tab | |
| keybind = cmd+shift+right=next_tab | |
| keybind = cmd+w=close_surface | |
| # Splits | |
| keybind = cmd+d=new_split:right | |
| keybind = cmd+shift+d=new_split:down | |
| keybind = cmd+alt+left=goto_split:left | |
| keybind = cmd+alt+right=goto_split:right | |
| keybind = cmd+alt+up=goto_split:top | |
| keybind = cmd+alt+down=goto_split:bottom | |
| # Font size | |
| keybind = cmd+plus=increase_font_size:1 | |
| keybind = cmd+minus=decrease_font_size:1 | |
| keybind = cmd+zero=reset_font_size | |
| # Quick terminal global hotkey | |
| keybind = global:ctrl+grave_accent=toggle_quick_terminal | |
| # Splits management | |
| keybind = cmd+shift+e=equalize_splits | |
| keybind = cmd+shift+f=toggle_split_zoom | |
| # Reload config | |
| keybind = cmd+shift+comma=reload_config | |
| # --- Performance --- | |
| # Generous scrollback (25MB) | |
| scrollback-limit = 25000000 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # 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) | |
| # | |
| # Ghostty keybindings required: | |
| # super+d = new_split:right | |
| # super+shift+d = new_split:down | |
| # super+alt+left = 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 | |
| git worktree add -b "$BRANCH_NAME" "$WORKTREE_DIR" "origin/$DEFAULT_BRANCH" >&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+Option+Left) | |
| key code 123 using {command down, option 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
Thanks!
It helps me a lot.