aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpablu <me@pablu.de>2026-09-17 17:05:03 +0200
committerpablu <me@pablu.de>2026-09-17 17:05:03 +0200
commitf8312a59c775c740d9af173fafa65ab7e731e03e (patch)
treea54a9274376f20bbb35f07bccfeab8aef542fa0e
parent0716da5eef605977b804f34f265979cf39be844b (diff)
update codex config to not contain local project dirs, update .tmux.conf only for gentoo, make tmux work with hyprland
-rw-r--r--.chezmoiignore2
-rw-r--r--codex/config.toml56
-rw-r--r--dot_codex/AGENTS.md15
-rw-r--r--dot_codex/modify_private_config.toml3
-rw-r--r--dot_codex/private_config.toml66
-rw-r--r--dot_config/herdr/config.toml6
-rw-r--r--dot_config/hypr/config/binds.lua54
-rw-r--r--dot_config/hypr/config/rules.lua10
-rw-r--r--dot_config/hypr/scripts/executable_herdr-smart-nav143
-rw-r--r--dot_config/hypr/scripts/executable_tmux-smart-nav105
-rw-r--r--dot_config/nvim/lua/hypr-nav.lua2
-rw-r--r--dot_config/nvim/lua/plugins/lsp.lua3
-rw-r--r--dot_config/private_jj/config.toml3
-rw-r--r--dot_config/waybar/config2
-rw-r--r--dot_tmux.conf.tmpl5
-rw-r--r--dot_zshrc.tmpl2
-rw-r--r--tmux/common.conf (renamed from dot_tmux.conf)0
-rw-r--r--tmux/gentoo.conf69
18 files changed, 457 insertions, 89 deletions
diff --git a/.chezmoiignore b/.chezmoiignore
index 7fda614..176db0a 100644
--- a/.chezmoiignore
+++ b/.chezmoiignore
@@ -1,4 +1,6 @@
README.md
+codex
+tmux
{{- if ne .chezmoi.hostname "gentoo" }}
.config/nvim/lua/hypr-nav.lua
.wallpaper
diff --git a/codex/config.toml b/codex/config.toml
new file mode 100644
index 0000000..ff7a423
--- /dev/null
+++ b/codex/config.toml
@@ -0,0 +1,56 @@
+model = "gpt-5.6-terra"
+model_reasoning_effort = "medium"
+
+approval_request = "on-request"
+sandbox_mode = "read-only"
+model_verbosity = "low"
+web_search = "cached"
+allow_login_shell = false
+
+developer_instructions = """
+All manual filesystem modifications must use Codex's apply_patch mechanism.
+
+Never create, modify, rename, or delete project files using Python, Python scripts,
+shell redirection, heredocs, sed -i, perl, awk, tee, dd, cat, printf, or another
+general-purpose command.
+
+Do not use a shell or programming-language interpreter as a workaround for
+apply_patch.
+
+If apply_patch cannot perform a requested edit, stop and explain the problem
+instead of using another mechanism to modify the filesystem.
+
+The purpose of this rule is that every manual code change must be presented as
+a reviewable file patch/diff before it is applied.
+
+Commands whose purpose is to generate files as their normal operation, such as
+formatters, compilers, code generators, or package managers, are exempt when
+the user has authorized running that command.
+"""
+
+[shell_environment_policy]
+inherit = "core"
+
+[features]
+hooks = false
+multi_agent = false
+# shell_snapshot = false
+# shell_tool = false
+memories = true
+deafult_mode_request_user_input = true
+code_mode_host = true
+
+[tui]
+theme = "catppuccin-mocha"
+status_line = ["model-with-reasoning", "current-dir", "git-branch", "run-state", "permissions", "context-used"]
+status_line_use_colors = true
+pet = "disabled"
+
+[tui.model_availability_nux]
+"gpt-5.5" = 4
+"gpt-5.6-sol" = 4
+gpt-6-astra = 4
+
+[notice]
+hide_rate_limit_model_nudge = true
+
diff --git a/dot_codex/AGENTS.md b/dot_codex/AGENTS.md
index d316b57..58c0b69 100644
--- a/dot_codex/AGENTS.md
+++ b/dot_codex/AGENTS.md
@@ -4,19 +4,24 @@ Your role:
- Explain architecture and tradeoffs clearly.
- Critically review code for correctness and maintainability.
- Challenge assumptions when appropriate.
-- Suggest improvements, but do not rewrite working code unnecessarily.
+- Suggest improvements and explain their tradeoffs.
Strict rules:
-- Do NOT modify files unless explicitly asked.
-- Do NOT run git commands unless explicitly asked.
-- Do NOT assume the user wants implementation.
-- Prefer discussion, reasoning, and review over code generation.
+- Default to discussion and review. Requests to fix, implement, or edit authorize changes within the requested scope. Questions about possible improvements do not authorize edits.
+- Do NOT write or modify production code unless explicitly asked. Permission to inspect code or run tests is not permission to implement changes.
+- Do NOT modify files outside the authorized scope.
+- Git and jj commands are allowed for inspection purposes, such as status, diff, and log. Other version-control operations require explicit authorization. NEVER create commits or push.
+- Relevant local tests are allowed. Test runners and other programs may write incidental temporary files and caches; this does not authorize you to manually create or modify files or invoke programs to generate or rewrite production code.
+- Keep authorized changes focused on the requested outcome. Avoid unrelated refactoring and preserve existing behavior outside that scope.
When reviewing:
- Focus on correctness first.
- Then API design and consistency.
- Then maintainability.
- Ignore formatting unless it impacts clarity.
+- Ground findings in the current code. Identify the affected location, triggering condition, and consequence.
+- Distinguish confirmed defects from hypotheses and optional improvements.
+- State what was verified and what remains unverified. Never imply tests passed unless they were run successfully.
Prefer idiomatic, simple solutions.
Avoid unnecessary abstractions.
diff --git a/dot_codex/modify_private_config.toml b/dot_codex/modify_private_config.toml
new file mode 100644
index 0000000..a5c2db2
--- /dev/null
+++ b/dot_codex/modify_private_config.toml
@@ -0,0 +1,3 @@
+{{/* chezmoi:modify-template */}}
+{{ include "codex/config.toml" -}}
+{{- regexFindAll `(?m)^\[projects\.[^\n]+\]\n(?:[^\[]*\n?)*` .chezmoi.stdin -1 | join "" -}}
diff --git a/dot_codex/private_config.toml b/dot_codex/private_config.toml
deleted file mode 100644
index e15610f..0000000
--- a/dot_codex/private_config.toml
+++ /dev/null
@@ -1,66 +0,0 @@
-model = "gpt-5.6-sol"
-model_reasoning_effort = "medium"
-
-approval_request = "on-request"
-sandbox_mode = "read-only"
-model_verbosity = "low"
-web_search = "cached"
-allow_login_shell = false
-
-[shell_environment_policy]
-inherit = "core"
-
-[features]
-hooks = false
-multi_agent = false
-# shell_snapshot = false
-# shell_tool = false
-memories = true
-
-[projects."/home/pablu/projects/zw"]
-trust_level = "trusted"
-
-[projects."/home/pablu/.config/nvim"]
-trust_level = "trusted"
-
-[projects."/home/pablu/projects/suchoi"]
-trust_level = "trusted"
-
-[projects."/home/pablu/projects/Logprep"]
-trust_level = "trusted"
-
-[projects."/home/pablu/.config/hypr"]
-trust_level = "trusted"
-
-[projects."/home/pablu/projects/qview"]
-trust_level = "trusted"
-
-[projects."/home/pablu/projects/tokonoma"]
-trust_level = "trusted"
-
-[projects."/home/pablu/projects/logprep-data-server"]
-trust_level = "trusted"
-
-[projects."/home/pablu/.codex"]
-trust_level = "trusted"
-
-[projects."/home/pablu"]
-trust_level = "trusted"
-
-[projects."/home/pablu/projects/domain-router"]
-trust_level = "trusted"
-
-[projects."/home/pablu/projects/pybug"]
-trust_level = "trusted"
-
-[projects."/home/pablu/projects/suchoi-v2"]
-trust_level = "trusted"
-
-[tui]
-theme = "catppuccin-mocha"
-status_line = ["model-with-reasoning", "current-dir", "git-branch", "run-state", "permissions", "context-used"]
-status_line_use_colors = true
-
-[tui.model_availability_nux]
-"gpt-5.5" = 4
-"gpt-5.6-sol" = 4
diff --git a/dot_config/herdr/config.toml b/dot_config/herdr/config.toml
index e117738..dc1ddd1 100644
--- a/dot_config/herdr/config.toml
+++ b/dot_config/herdr/config.toml
@@ -57,7 +57,11 @@ focus_pane_down = "prefix+j"
focus_pane_up = "prefix+k"
focus_pane_right = "prefix+l"
-# Direct Ctrl+HJKL is handled by herdr-splits below.
+# Direct pane swapping, local to Herdr.
+swap_pane_left = "ctrl+h"
+swap_pane_down = "ctrl+j"
+swap_pane_up = "ctrl+k"
+swap_pane_right = "ctrl+l"
# [[keys.command]]
# key = "ctrl+h"
diff --git a/dot_config/hypr/config/binds.lua b/dot_config/hypr/config/binds.lua
index a5a9e6b..a72777f 100644
--- a/dot_config/hypr/config/binds.lua
+++ b/dot_config/hypr/config/binds.lua
@@ -52,6 +52,28 @@ local function ask_direct_nvim(win, direction)
return response:find("success", 1, true) ~= nil
end
+local function ask_tmux(win, direction)
+ local pid = tonumber(win.pid)
+
+ if not pid then
+ return false
+ end
+
+ local helper = (os.getenv("HOME") or "")
+ .. "/.config/hypr/scripts/tmux-smart-nav"
+
+ local handle = io.popen(string.format("%s %s %d", helper, direction, pid))
+
+ if not handle then
+ return false
+ end
+
+ local result = handle:read("*a")
+ handle:close()
+
+ return result:find("handled", 1, true) ~= nil
+end
+
local function smart_nav(direction)
return function()
local win = hl.get_active_window()
@@ -67,25 +89,29 @@ local function smart_nav(direction)
local title = (win.title or ""):lower()
- if title == "herdr" then
+ if ask_tmux(win, direction) then
+ return
+ end
+
+ -- Herdr now uses changing window titles. The helper checks whether its
+ -- foreground client belongs to this terminal before attempting navigation.
+ local pid = tonumber(win.pid)
+ if pid then
local helper = (os.getenv("HOME") or "")
.. "/.config/hypr/scripts/herdr-smart-nav"
+ local handle = io.popen(string.format("%q %s %d", helper, direction, pid))
- local handle = io.popen(helper .. " " .. direction)
+ if handle then
+ local result = handle:read("*a"):match("^%s*(.-)%s*$")
+ handle:close()
- if not handle then
- focus_hyprland(direction)
- return
+ if result == "handled" then
+ return
+ elseif result == "hyprland" then
+ focus_hyprland(direction)
+ return
+ end
end
-
- local result = handle:read("*a")
- handle:close()
-
- if result:find("hyprland", 1, true) then
- focus_hyprland(direction)
- end
-
- return
end
if is_direct_nvim(title) and ask_direct_nvim(win, direction) then
diff --git a/dot_config/hypr/config/rules.lua b/dot_config/hypr/config/rules.lua
index 0e17e4a..ca5fe88 100644
--- a/dot_config/hypr/config/rules.lua
+++ b/dot_config/hypr/config/rules.lua
@@ -73,3 +73,13 @@ hl.window_rule({
no_blur = true,
no_shadow = true,
})
+
+hl.window_rule({
+ name = "ghostty-fullscreen-opaque",
+ match = {
+ class = "com.mitchellh.ghostty",
+ fullscreen = true,
+ },
+ force_rgbx = true,
+ no_blur = true,
+})
diff --git a/dot_config/hypr/scripts/executable_herdr-smart-nav b/dot_config/hypr/scripts/executable_herdr-smart-nav
new file mode 100644
index 0000000..36d27aa
--- /dev/null
+++ b/dot_config/hypr/scripts/executable_herdr-smart-nav
@@ -0,0 +1,143 @@
+#!/usr/bin/env bash
+
+set -u
+
+direction="${1:-}"
+terminal_pid="${2:-}"
+
+case "$direction" in
+ left|right|up|down)
+ ;;
+ *)
+ printf 'usage: %s <left|right|up|down> <terminal-pid>\n' "$0" >&2
+ exit 2
+ ;;
+esac
+
+# Window titles can change with the workspace. Find a foreground Herdr
+# process belonging to this terminal instead of relying on its title.
+is_in_terminal() {
+ local pid="$1"
+ local parent
+
+ while [[ "$pid" =~ ^[0-9]+$ && "$pid" -gt 1 ]]; do
+ [[ "$pid" == "$terminal_pid" ]] && return 0
+ parent="$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d '[:space:]')"
+ [[ -n "$parent" && "$parent" != "$pid" ]] || return 1
+ pid="$parent"
+ done
+
+ return 1
+}
+
+herdr_found=false
+if [[ "$terminal_pid" =~ ^[0-9]+$ ]]; then
+ while read -r pid process_group foreground_group; do
+ # The server has no foreground terminal; a suspended client is not active.
+ if [[ "$foreground_group" -gt 0 && "$process_group" == "$foreground_group" ]] &&
+ is_in_terminal "$pid"; then
+ herdr_found=true
+ break
+ fi
+ done < <(ps -C herdr -o pid=,pgid=,tpgid= 2>/dev/null)
+fi
+
+if [[ "$herdr_found" != true ]]; then
+ printf 'not-herdr\n'
+ exit
+fi
+
+herdr_bin="${HERDR_BIN_PATH:-$HOME/.local/share/mise/installs/herdr/latest/herdr}"
+
+if [[ ! -x "$herdr_bin" ]]; then
+ printf 'hyprland\n'
+ exit
+fi
+
+handled() {
+ printf 'handled\n'
+ exit
+}
+
+fall_through() {
+ printf 'hyprland\n'
+ exit
+}
+
+current="$("$herdr_bin" pane current --current 2>/dev/null)" || {
+ fall_through
+}
+
+pane_id="$(jq -r '.result.pane.pane_id // empty' <<<"$current")"
+
+if [[ -z "$pane_id" ]]; then
+ fall_through
+fi
+
+process_info="$(
+ "$herdr_bin" pane process-info --pane "$pane_id" 2>/dev/null || true
+)"
+
+nvim_pid="$(
+ jq -r '
+ [
+ (.result.process_info.foreground_processes // [])[]
+ | select(
+ ((.name // "") | ascii_downcase) == "nvim"
+ or ((.name // "") | ascii_downcase) == "vim"
+ )
+ | .pid
+ ][0] // empty
+ ' <<<"$process_info" 2>/dev/null
+)"
+
+if [[ -n "$nvim_pid" ]]; then
+ safe_pane_id="${pane_id//[^a-zA-Z0-9_.-]/_}"
+ runtime_dir="${XDG_RUNTIME_DIR:-/tmp}"
+
+ socket_path="$runtime_dir/nvim-hypr-nav-pane-$safe_pane_id.sock"
+
+ response="$(
+ printf 'nav:%s\n' "$direction" |
+ socat -T 0.25 -,ignoreeof "UNIX-CONNECT:$socket_path" 2>/dev/null ||
+ true
+ )"
+
+ if [[ "$response" == *success* ]]; then
+ handled
+ fi
+fi
+
+neighbor="$(
+ "$herdr_bin" pane neighbor \
+ --pane "$pane_id" \
+ --direction "$direction" \
+ 2>/dev/null
+)" || {
+ fall_through
+}
+
+# A zoomed pane deliberately falls through to Hyprland. Do not expose or
+# navigate to another Herdr pane.
+if jq -e '.result.neighbor.layout.zoomed == true' \
+ >/dev/null 2>&1 <<<"$neighbor"; then
+ fall_through
+fi
+
+neighbor_id="$(
+ jq -r '.result.neighbor.neighbor_pane_id // empty' <<<"$neighbor"
+)"
+
+# Herdr 0.7.5 returns the current pane itself when there is no neighbour.
+if [[ -z "$neighbor_id" || "$neighbor_id" == "$pane_id" ]]; then
+ fall_through
+fi
+
+if "$herdr_bin" pane focus \
+ --pane "$pane_id" \
+ --direction "$direction" \
+ >/dev/null 2>&1; then
+ handled
+else
+ fall_through
+fi
diff --git a/dot_config/hypr/scripts/executable_tmux-smart-nav b/dot_config/hypr/scripts/executable_tmux-smart-nav
new file mode 100644
index 0000000..4275462
--- /dev/null
+++ b/dot_config/hypr/scripts/executable_tmux-smart-nav
@@ -0,0 +1,105 @@
+#!/usr/bin/env bash
+
+set -u
+
+direction="${1:-}"
+terminal_pid="${2:-}"
+
+case "$direction" in
+ left)
+ tmux_key="C-h"
+ ;;
+ right)
+ tmux_key="C-l"
+ ;;
+ up)
+ tmux_key="C-k"
+ ;;
+ down)
+ tmux_key="C-j"
+ ;;
+ *)
+ printf 'usage: %s <left|right|up|down> <terminal-pid>\n' "$0" >&2
+ exit 2
+ ;;
+esac
+
+if [[ ! "$terminal_pid" =~ ^[0-9]+$ ]] || ! command -v tmux >/dev/null 2>&1; then
+ printf 'hyprland\n'
+ exit
+fi
+
+# A tmux client is a descendant of the terminal process that owns it. This
+# avoids acting on a client in another Ghostty window.
+is_descendant_of() {
+ local pid="$1"
+ local parent
+
+ while [[ "$pid" =~ ^[0-9]+$ && "$pid" -gt 1 ]]; do
+ if [[ "$pid" == "$terminal_pid" ]]; then
+ return 0
+ fi
+
+ parent="$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d '[:space:]')"
+ [[ -n "$parent" && "$parent" != "$pid" ]] || return 1
+ pid="$parent"
+ done
+
+ return 1
+}
+
+client_tty=""
+while read -r client_pid tty; do
+ if is_descendant_of "$client_pid"; then
+ client_tty="$tty"
+ break
+ fi
+done < <(tmux list-clients -F '#{client_pid} #{client_tty}' 2>/dev/null)
+
+if [[ -z "$client_tty" ]]; then
+ printf 'hyprland\n'
+ exit
+fi
+
+pane_id="$(tmux display-message -p -c "$client_tty" '#{pane_id}' 2>/dev/null)"
+pane_command="$(tmux display-message -p -c "$client_tty" '#{pane_current_command}' 2>/dev/null)"
+
+# Ghostty has one title for the whole tmux window, so it cannot identify the
+# focused pane's process. Ask Neovim through a pane-specific socket only when
+# tmux says this exact pane is running it.
+case "${pane_command,,}" in
+ nvim|vim)
+ safe_pane_id="${pane_id//[^a-zA-Z0-9_.-]/_}"
+ socket_path="${XDG_RUNTIME_DIR:-/tmp}/nvim-hypr-nav-pane-${safe_pane_id}.sock"
+ response="$(
+ printf 'nav:%s\n' "$direction" |
+ socat -T 0.25 -,ignoreeof "UNIX-CONNECT:$socket_path" 2>/dev/null || true
+ )"
+
+ if [[ "$response" == *success* ]]; then
+ printf 'handled\n'
+ exit
+ fi
+ ;;
+esac
+
+# Send the directional key through the matching client so tmux resolves it
+# against that client's own active pane. Its existing C-h/j/k/l bindings also
+# retain the configured Neovim handoff.
+at_edge="$(tmux display-message -p -c "$client_tty" "#{pane_at_${direction}}" 2>/dev/null)"
+
+if [[ -z "$pane_id" || "$at_edge" == "1" ]]; then
+ printf 'hyprland\n'
+ exit
+fi
+
+if tmux send-keys -c "$client_tty" -K "$tmux_key" >/dev/null 2>&1; then
+ selected_pane_id="$(tmux display-message -p -c "$client_tty" '#{pane_id}' 2>/dev/null)"
+
+ if [[ "$selected_pane_id" != "$pane_id" ]]; then
+ printf 'handled\n'
+ exit
+ fi
+fi
+
+printf 'hyprland\n'
diff --git a/dot_config/nvim/lua/hypr-nav.lua b/dot_config/nvim/lua/hypr-nav.lua
index 3224b1d..102072a 100644
--- a/dot_config/nvim/lua/hypr-nav.lua
+++ b/dot_config/nvim/lua/hypr-nav.lua
@@ -31,7 +31,7 @@ local function get_hyprland_stable_id()
end
local function get_socket_path()
- local pane_id = vim.env.HERDR_PANE_ID
+ local pane_id = vim.env.HERDR_PANE_ID or vim.env.TMUX_PANE
if pane_id and pane_id ~= "" then
return string.format(
diff --git a/dot_config/nvim/lua/plugins/lsp.lua b/dot_config/nvim/lua/plugins/lsp.lua
index 746a48b..57ec231 100644
--- a/dot_config/nvim/lua/plugins/lsp.lua
+++ b/dot_config/nvim/lua/plugins/lsp.lua
@@ -18,7 +18,8 @@ return {
"nil_ls",
"gleam",
"ty",
- "buf_ls"
+ "buf_ls",
+ "ts_ls"
})
diff --git a/dot_config/private_jj/config.toml b/dot_config/private_jj/config.toml
index a619a67..6f16d0b 100644
--- a/dot_config/private_jj/config.toml
+++ b/dot_config/private_jj/config.toml
@@ -6,3 +6,6 @@ email = "me@pablu.de"
[aliases]
push = ["util", "exec", "--", "jj-push"]
+
+[ui]
+diff-editor = ":builtin"
diff --git a/dot_config/waybar/config b/dot_config/waybar/config
index c89753d..0139993 100644
--- a/dot_config/waybar/config
+++ b/dot_config/waybar/config
@@ -1,7 +1,7 @@
{
"layer": "top",
"position": "top",
- "height": 28,
+ "height": 24,
"modules-left": ["hyprland/workspaces"],
"modules-center": ["hyprland/window"],
diff --git a/dot_tmux.conf.tmpl b/dot_tmux.conf.tmpl
new file mode 100644
index 0000000..6c53841
--- /dev/null
+++ b/dot_tmux.conf.tmpl
@@ -0,0 +1,5 @@
+{{- if eq .chezmoi.hostname "gentoo" -}}
+{{ include "tmux/gentoo.conf" -}}
+{{- else -}}
+{{ include "tmux/common.conf" -}}
+{{- end -}}
diff --git a/dot_zshrc.tmpl b/dot_zshrc.tmpl
index 302201d..7ca280e 100644
--- a/dot_zshrc.tmpl
+++ b/dot_zshrc.tmpl
@@ -49,6 +49,8 @@ alias ls='eza --icons=always'
eval "$(mise activate zsh)"
+[[ -r ~/.config/secrets/openrouter.env ]] && source ~/.config/secrets/openrouter.env
+
path+=("/home/pablu/.cargo/bin")
export PATH
{{ else if eq .chezmoi.os "darwin" }}
diff --git a/dot_tmux.conf b/tmux/common.conf
index f3c03bd..f3c03bd 100644
--- a/dot_tmux.conf
+++ b/tmux/common.conf
diff --git a/tmux/gentoo.conf b/tmux/gentoo.conf
new file mode 100644
index 0000000..9643dc6
--- /dev/null
+++ b/tmux/gentoo.conf
@@ -0,0 +1,69 @@
+# remap prefix
+set-option -g prefix C-Space
+unbind C-Space
+bind-key C-Space send-prefix
+
+set -g mouse on
+
+set -g base-index 1
+setw -g pane-base-index 1
+
+set -g renumber-windows on
+set -g set-clipboard on
+set -g pane-border-status off
+set -g history-limit 100000
+
+set -g set-titles on
+set -g set-titles-string "#S:#W"
+# Keep project-style session names intact in the default status-left format.
+set -g status-left-length 30
+
+set -g automatic-rename off
+set -g allow-rename off
+
+# Proper RGB/truecolor support in Ghostty.
+set -as terminal-features ",xterm-ghostty:RGB"
+set -as terminal-features ',*:hyperlinks'
+
+# Forward terminal focus changes to applications.
+# Useful for Neovim FocusGained/FocusLost, auto-reloading files, etc.
+set -g focus-events on
+
+# Don't introduce noticeable Escape latency.
+set -sg escape-time 10
+
+# split panes
+bind / split-window -hc "#{pane_current_path}"
+bind - split-window -vc "#{pane_current_path}"
+bind c new-window -c "#{pane_current_path}"
+bind z resize-pane -Z
+bind x kill-pane
+bind Tab last-window
+# Return to the previously used session/project.
+bind B switch-client -l
+# Choose or create a project session in a compact popup picker.
+bind w display-popup -E -B "~/.local/bin/tmux-project-switcher"
+
+bind -r "<" swap-pane -d -t -1
+bind -r ">" swap-pane -d -t +1
+
+bind r source-file ~/.tmux.conf
+
+bind -n C-h select-pane -L
+bind -n C-j select-pane -D
+bind -n C-k select-pane -U
+bind -n C-l select-pane -R
+
+# Plugins
+set -g @plugin 'tmux-plugins/tpm'
+set -g @plugin 'tmux-plugins/tmux-resurrect'
+set -g @plugin 'tmux-plugins/tmux-continuum'
+
+# Automatically restore the latest saved environment
+set -g @continuum-restore 'on'
+
+# Save every 5 minutes instead of the default 15.
+set -g @continuum-save-interval '5'
+
+# Keep TPM last
+run '~/.tmux/plugins/tpm/tpm'