Last active
April 13, 2026 14:10
-
-
Save romgenie/a6acd389dc53c78802b971058cd281f1 to your computer and use it in GitHub Desktop.
WezTerm config — Claude Code color scheme, SSH launcher with per-host colors, Codex/Claude Code quick-launch, numbered launcher menu, custom keybindings
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
| -- ~/.wezterm.lua | |
| -- Cross-platform WezTerm config (Windows / macOS / Linux). | |
| -- Gist: https://gist.github.com/romgenie/a6acd389dc53c78802b971058cd281f1 | |
| local wezterm = require 'wezterm' | |
| local mux = wezterm.mux | |
| local act = wezterm.action | |
| local config = wezterm.config_builder() | |
| -- ============================================================ | |
| -- OS detection | |
| -- ============================================================ | |
| local is_windows = wezterm.target_triple:find('windows') ~= nil | |
| local is_mac = wezterm.target_triple:find('darwin') ~= nil | |
| local is_linux = not is_windows and not is_mac | |
| -- ============================================================ | |
| -- Shell, font, and renderer (platform-specific) | |
| -- ============================================================ | |
| if is_windows then | |
| config.default_prog = { 'pwsh.exe', '-NoLogo' } | |
| config.font = wezterm.font_with_fallback { | |
| 'Cascadia Mono', | |
| 'Cascadia Code', | |
| 'Consolas', | |
| } | |
| config.front_end = 'OpenGL' -- more stable than WebGpu on Windows | |
| elseif is_mac then | |
| config.default_prog = { '/bin/zsh', '-l' } | |
| -- Menlo is always available on macOS; SF Mono requires manual install | |
| -- (it's buried inside Terminal.app's bundle and not in /Library/Fonts). | |
| config.font = wezterm.font_with_fallback { | |
| 'Menlo', | |
| 'Monaco', | |
| } | |
| -- Make Option act as a pure modifier (Alt), not a text composition key. | |
| -- This fixes Ctrl+Alt+Arrow not auto-repeating properly on macOS. | |
| config.send_composed_key_when_left_alt_is_pressed = false | |
| config.send_composed_key_when_right_alt_is_pressed = false | |
| else | |
| config.default_prog = { '/bin/bash', '-l' } | |
| config.font = wezterm.font_with_fallback { | |
| 'JetBrains Mono', | |
| 'Fira Code', | |
| 'monospace', | |
| } | |
| end | |
| config.font_size = 12.0 | |
| -- ============================================================ | |
| -- Color scheme (cross-platform) | |
| -- ============================================================ | |
| -- Built from Claude Code's actual UI palette (extracted from the | |
| -- dark theme). Claude Code uses truecolor for its own rendering; | |
| -- these ANSI mappings match its accent colors for consistency. | |
| config.color_schemes = { | |
| ['Claude Code'] = { | |
| background = '#0C0C0C', | |
| foreground = '#CCCCCC', | |
| cursor_bg = '#d77757', -- Claude's brand terracotta | |
| cursor_fg = '#111111', | |
| cursor_border = '#d77757', | |
| selection_bg = '#264f78', | |
| selection_fg = '#ffffff', | |
| ansi = { | |
| '#373737', -- black | |
| '#ff6b80', -- red (error) | |
| '#4eba65', -- green (success) | |
| '#ffc107', -- yellow (warning) | |
| '#2563eb', -- blue | |
| '#af87ff', -- magenta (autoAccept) | |
| '#0891b2', -- cyan | |
| '#999999', -- white (inactive) | |
| }, | |
| brights = { | |
| '#505050', -- bright black | |
| '#dc2626', -- bright red | |
| '#38a660', -- bright green | |
| '#ca8a04', -- bright yellow | |
| '#b1b9f9', -- bright blue (permission) | |
| '#db2777', -- bright magenta | |
| '#00cccc', -- bright cyan | |
| '#ffffff', -- bright white | |
| }, | |
| }, | |
| } | |
| config.color_scheme = 'Claude Code' | |
| -- ============================================================ | |
| -- Appearance (cross-platform) | |
| -- ============================================================ | |
| config.window_background_opacity = 1.0 | |
| config.window_decorations = 'TITLE | RESIZE' | |
| config.hide_tab_bar_if_only_one_tab = false | |
| config.use_fancy_tab_bar = false -- retro: respects tab_max_width, edge-to-edge | |
| config.tab_max_width = 60 | |
| config.window_padding = { left = 0, right = 0, top = 0, bottom = 0 } | |
| config.tab_bar_at_bottom = true -- tabs at bottom; all status in title bar at top | |
| config.exit_behavior = 'CloseOnCleanExit' | |
| -- ============================================================ | |
| -- Session persistence via multiplexer (cross-platform) | |
| -- ============================================================ | |
| config.unix_domains = { { name = 'main' } } | |
| config.default_gui_startup_args = { 'connect', 'main' } | |
| -- ============================================================ | |
| -- SSH launcher entries (cross-platform) | |
| -- ============================================================ | |
| -- Suppress WezTerm's bundled libssh (broken on Windows; unnecessary elsewhere) | |
| config.ssh_domains = {} | |
| local menu_styles = { | |
| ['PowerShell'] = { color = '#dc2626', icon = wezterm.nerdfonts.md_powershell }, | |
| ['Command Prompt'] = { color = '#2563eb', icon = wezterm.nerdfonts.cod_terminal }, | |
| ['zsh'] = { color = '#9ece6a', icon = wezterm.nerdfonts.dev_terminal }, | |
| ['bash'] = { color = '#ffc107', icon = wezterm.nerdfonts.dev_terminal }, | |
| ['Codex'] = { color = '#ffffff', icon = wezterm.nerdfonts.fa_code }, | |
| ['Claude Code'] = { color = '#d77757', icon = wezterm.nerdfonts.md_robot }, | |
| ['train'] = { color = '#f59e0b', icon = wezterm.nerdfonts.md_desktop_tower }, | |
| ['completetrain-b550'] = { color = '#f59e0b', icon = wezterm.nerdfonts.md_desktop_tower }, | |
| ['completetrain'] = { color = '#f59e0b', icon = wezterm.nerdfonts.md_desktop_tower }, | |
| ['b550'] = { color = '#f59e0b', icon = wezterm.nerdfonts.md_desktop_tower }, | |
| ['router'] = { color = '#38a660', icon = wezterm.nerdfonts.md_router_wireless }, | |
| ['openwrt'] = { color = '#38a660', icon = wezterm.nerdfonts.md_router_wireless }, | |
| ['router.complete.tech'] = { color = '#38a660', icon = wezterm.nerdfonts.md_router_wireless }, | |
| ['pi'] = { color = '#c51a4a', icon = wezterm.nerdfonts.md_raspberry_pi }, | |
| ['pi-complete'] = { color = '#c51a4a', icon = wezterm.nerdfonts.md_raspberry_pi }, | |
| ['complete.tech'] = { color = '#00cccc', icon = wezterm.nerdfonts.md_server }, | |
| ['steamanddip.com'] = { color = '#7dcfff', icon = wezterm.nerdfonts.md_server }, | |
| ['aiascension.tech'] = { color = '#bb9af7', icon = wezterm.nerdfonts.md_server }, | |
| ['agentonomics.app'] = { color = '#9ece6a', icon = wezterm.nerdfonts.md_server }, | |
| ['webhook.complete.tech'] = { color = '#f7768e', icon = wezterm.nerdfonts.md_server }, | |
| } | |
| local function menu_label(num, name) | |
| local style = menu_styles[name] | |
| if not style then | |
| return num .. '. ' .. name | |
| end | |
| return wezterm.format { | |
| { Foreground = { Color = style.color } }, | |
| { Text = num .. '. ' .. style.icon .. ' ' .. name }, | |
| } | |
| end | |
| -- Parse ~/.ssh/config and produce a numbered launcher entry per Host block. | |
| -- On Windows: wraps in pwsh (ConPTY needs a shell parent for ssh.exe). | |
| -- On macOS/Linux: launches ssh directly (no wrapper needed). | |
| local function ssh_launch_entries(start_num) | |
| local entries = {} | |
| local seen = {} | |
| local skip_hosts = { | |
| router = true, | |
| openwrt = true, | |
| ['router.complete.tech'] = true, | |
| ['192.168.1.1'] = true, | |
| } | |
| local path = wezterm.home_dir .. '/.ssh/config' | |
| local f = io.open(path, 'r') | |
| if not f then return entries end | |
| for line in f:lines() do | |
| local host_patterns = line:match('^%s*[Hh]ost%s+(.+)%s*$') | |
| if host_patterns then | |
| host_patterns = host_patterns:gsub('%s+#.*$', '') | |
| end | |
| local host = nil | |
| if host_patterns then | |
| for pattern in host_patterns:gmatch('%S+') do | |
| if not pattern:find('[*?]') then | |
| host = pattern | |
| break | |
| end | |
| end | |
| end | |
| if host and skip_hosts[host] then | |
| host = nil | |
| end | |
| if host and not seen[host] then | |
| seen[host] = true | |
| local num = #entries + start_num | |
| local args | |
| if is_windows then | |
| local inner = string.format( | |
| 'wezterm cli set-tab-title "%s"; ssh.exe %s; exit $LASTEXITCODE', | |
| host, host | |
| ) | |
| args = { 'pwsh.exe', '-NoLogo', '-NoProfile', '-Command', inner } | |
| else | |
| args = { '/bin/zsh', '-l', '-c', string.format( | |
| 'wezterm cli set-tab-title "%s"; ssh %s', | |
| host, host | |
| )} | |
| end | |
| table.insert(entries, { | |
| label = menu_label(num, host), | |
| args = args, | |
| }) | |
| end | |
| end | |
| f:close() | |
| return entries | |
| end | |
| -- ============================================================ | |
| -- Launcher menu (platform-specific shells + agents, then SSH) | |
| -- ============================================================ | |
| local launch_entries = {} | |
| if is_windows then | |
| launch_entries = { | |
| { | |
| label = menu_label(1, 'PowerShell'), | |
| args = { 'pwsh.exe', '-NoLogo' }, | |
| }, | |
| { | |
| label = menu_label(2, 'Command Prompt'), | |
| args = { 'cmd.exe' }, | |
| }, | |
| { | |
| label = menu_label(3, 'Codex'), | |
| args = { 'pwsh.exe', '-NoLogo', '-NoProfile', '-Command', | |
| [=[wezterm cli set-tab-title "Codex"; codex --dangerously-bypass-approvals-and-sandbox]=], | |
| }, | |
| }, | |
| { | |
| label = menu_label(4, 'Claude Code'), | |
| args = { 'pwsh.exe', '-NoLogo', '-NoProfile', '-Command', | |
| [=[wezterm cli set-tab-title "Claude Code"; Start-Job { Start-Sleep -Milliseconds 750; Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('{ENTER}') } | Out-Null; claude --dangerously-skip-permissions --permission-mode bypassPermissions --effort high]=], | |
| }, | |
| }, | |
| } | |
| elseif is_mac then | |
| -- Claude Code and Codex run in Docker containers on macOS. | |
| local docker_dir = '/Users/completetech/Documents/docker/ai-setup' | |
| launch_entries = { | |
| { | |
| label = menu_label(1, 'zsh'), | |
| args = { '/bin/zsh', '-l' }, | |
| }, | |
| { | |
| label = menu_label(2, 'bash'), | |
| args = { '/bin/bash', '-l' }, | |
| }, | |
| { | |
| label = menu_label(3, 'Codex'), | |
| args = { '/bin/zsh', '-l', '-c', | |
| 'wezterm cli set-tab-title "Codex"; cd "' .. docker_dir .. '" && docker compose exec -u dev codex bash -lc "cd /workspace && codex"' }, | |
| }, | |
| { | |
| label = menu_label(4, 'Claude Code'), | |
| args = { '/bin/zsh', '-l', '-c', | |
| 'wezterm cli set-tab-title "Claude Code"; cd "' .. docker_dir .. '" && docker compose exec -u dev claude bash -lc "cd /workspace && claude"' }, | |
| }, | |
| } | |
| else -- Linux: mirror Windows structure (primary shell + alt shell + agents) | |
| launch_entries = { | |
| { | |
| label = menu_label(1, 'bash'), | |
| args = { '/bin/bash', '-l' }, | |
| }, | |
| { | |
| label = menu_label(2, 'zsh'), | |
| args = { '/bin/zsh', '-l' }, | |
| }, | |
| { | |
| label = menu_label(3, 'Codex'), | |
| args = { '/bin/bash', '-l', '-c', | |
| 'wezterm cli set-tab-title "Codex"; codex --dangerously-bypass-approvals-and-sandbox' }, | |
| }, | |
| { | |
| label = menu_label(4, 'Claude Code'), | |
| args = { '/bin/bash', '-l', '-c', | |
| 'wezterm cli set-tab-title "Claude Code"; claude --dangerously-skip-permissions --permission-mode bypassPermissions --effort high' }, | |
| }, | |
| } | |
| end | |
| -- Append auto-discovered SSH hosts from ~/.ssh/config | |
| for _, entry in ipairs(ssh_launch_entries(#launch_entries + 1)) do | |
| table.insert(launch_entries, entry) | |
| end | |
| -- Hand-rolled webhook convenience entry (connects to complete.tech, cd ~/webhook) | |
| local webhook_num = #launch_entries + 1 | |
| if is_windows then | |
| table.insert(launch_entries, { | |
| label = menu_label(webhook_num, 'webhook.complete.tech'), | |
| args = { | |
| 'pwsh.exe', '-NoLogo', '-NoProfile', '-Command', | |
| [=[wezterm cli set-tab-title "webhook.complete.tech"; ssh.exe -t complete.tech "cd ~/webhook && exec $SHELL -l"; exit $LASTEXITCODE]=], | |
| }, | |
| }) | |
| else | |
| -- Single-quote the remote command so $SHELL is expanded on the REMOTE | |
| -- host (whatever shell is set there), not by the local zsh. | |
| table.insert(launch_entries, { | |
| label = menu_label(webhook_num, 'webhook.complete.tech'), | |
| args = { '/bin/zsh', '-l', '-c', | |
| [=[wezterm cli set-tab-title "webhook.complete.tech"; ssh -t complete.tech 'cd ~/webhook && exec $SHELL -l']=] }, | |
| }) | |
| end | |
| config.launch_menu = launch_entries | |
| -- Hand-rolled router entry kept at the end of the launcher menu. | |
| local router_num = #config.launch_menu + 1 | |
| if is_windows then | |
| table.insert(config.launch_menu, { | |
| label = menu_label(router_num, 'router'), | |
| args = { | |
| 'pwsh.exe', '-NoLogo', '-NoProfile', '-Command', | |
| [=[wezterm cli set-tab-title "router"; ssh.exe router; exit $LASTEXITCODE]=], | |
| }, | |
| }) | |
| else | |
| table.insert(config.launch_menu, { | |
| label = menu_label(router_num, 'router'), | |
| args = { '/bin/zsh', '-l', '-c', | |
| [=[wezterm cli set-tab-title "router"; ssh router]=] }, | |
| }) | |
| end | |
| -- ============================================================ | |
| -- Workspaces (cross-platform) | |
| -- ============================================================ | |
| wezterm.on('spawn-dev-workspace', function(window, pane) | |
| local tab, left, win = mux.spawn_window { | |
| workspace = 'dev', | |
| cwd = wezterm.home_dir .. '/projects', | |
| } | |
| left:send_text 'nvim .\n' | |
| local top_right = left:split { direction = 'Right', size = 0.4 } | |
| top_right:send_text 'npm run dev\n' | |
| local bottom_right = top_right:split { direction = 'Bottom', size = 0.5 } | |
| bottom_right:send_text 'git status\n' | |
| mux.set_active_workspace 'dev' | |
| end) | |
| -- ============================================================ | |
| -- Tab titles: icons + colors (cross-platform) | |
| -- ============================================================ | |
| local tab_icons = { | |
| ['PowerShell'] = wezterm.nerdfonts.md_powershell, | |
| ['Windows PowerShell'] = wezterm.nerdfonts.md_powershell, | |
| ['cmd'] = wezterm.nerdfonts.cod_terminal, | |
| ['zsh'] = wezterm.nerdfonts.dev_terminal, | |
| ['bash'] = wezterm.nerdfonts.dev_terminal, | |
| ['Codex'] = wezterm.nerdfonts.fa_code, | |
| ['Claude Code'] = wezterm.nerdfonts.md_robot, | |
| ['train'] = wezterm.nerdfonts.md_desktop_tower, | |
| ['completetrain'] = wezterm.nerdfonts.md_desktop_tower, | |
| ['pi'] = wezterm.nerdfonts.md_raspberry_pi, | |
| ['router'] = wezterm.nerdfonts.md_router_wireless, | |
| ['openwrt'] = wezterm.nerdfonts.md_router_wireless, | |
| ['router.complete.tech'] = wezterm.nerdfonts.md_router_wireless, | |
| ['complete.tech'] = wezterm.nerdfonts.md_server, | |
| ['pi-complete'] = wezterm.nerdfonts.md_raspberry_pi, | |
| ['completetrain-b550'] = wezterm.nerdfonts.md_desktop_tower, | |
| ['steamanddip.com'] = wezterm.nerdfonts.md_server, | |
| ['aiascension.tech'] = wezterm.nerdfonts.md_server, | |
| ['agentonomics.app'] = wezterm.nerdfonts.md_server, | |
| ['webhook.complete.tech'] = wezterm.nerdfonts.md_server, | |
| } | |
| local tab_colors = { | |
| -- Shells | |
| ['PowerShell'] = '#dc2626', -- red | |
| ['Windows PowerShell'] = '#dc2626', -- red | |
| ['cmd'] = '#2563eb', -- blue | |
| ['zsh'] = '#9ece6a', -- green | |
| ['bash'] = '#ffc107', -- yellow | |
| -- Agents | |
| ['Codex'] = '#ffffff', -- white | |
| ['Claude Code'] = '#d77757', -- Claude terracotta | |
| -- SSH hosts | |
| ['train'] = '#f59e0b', -- amber | |
| ['completetrain'] = '#f59e0b', -- amber | |
| ['pi'] = '#c51a4a', -- raspberry | |
| ['router'] = '#38a660', -- green | |
| ['openwrt'] = '#38a660', -- green | |
| ['router.complete.tech'] = '#38a660', -- green | |
| ['complete.tech'] = '#00cccc', -- teal | |
| ['pi-complete'] = '#c51a4a', -- raspberry | |
| ['completetrain-b550'] = '#f59e0b', -- amber | |
| ['steamanddip.com'] = '#7dcfff', -- cyan | |
| ['aiascension.tech'] = '#bb9af7', -- violet | |
| ['agentonomics.app'] = '#9ece6a', -- green | |
| ['webhook.complete.tech'] = '#f7768e', -- red/pink | |
| } | |
| wezterm.on('format-tab-title', function(tab, tabs, panes, conf, hover, max_width) | |
| local pane_info = tab.active_pane | |
| local title = nil | |
| local color_key = nil | |
| -- 1. Tab title set explicitly via `wezterm cli set-tab-title` | |
| if tab.tab_title and tab.tab_title ~= '' then | |
| title = tostring(tab.tab_title) | |
| if tab_colors[title] then | |
| color_key = title | |
| end | |
| end | |
| -- 2. pane.title cleanup (handles plain shell tabs) | |
| if not title and pane_info and pane_info.title then | |
| local t = tostring(pane_info.title) | |
| if t ~= '' and t:lower() ~= 'wezterm' and not t:find('^wezterm:') then | |
| title = t:gsub('^.*[\\/]', ''):gsub('%.[Ee][Xx][Ee]$', '') | |
| local lower = title:lower() | |
| if lower == 'pwsh' then title = 'PowerShell' | |
| elseif lower == 'powershell' then title = 'Windows PowerShell' | |
| elseif lower == 'zsh' then title = 'zsh' | |
| elseif lower == 'bash' then title = 'bash' | |
| end | |
| if tab_colors[title] then color_key = title end | |
| end | |
| end | |
| if not title or title == '' then title = 'shell' end | |
| if #title > 32 then title = title:sub(1, 29) .. '...' end | |
| local idx = tostring((tab.tab_index or 0) + 1) | |
| local icon = tab_icons[title] or '' | |
| if icon ~= '' then icon = icon .. ' ' end | |
| local label = ' ' .. idx .. ' ' .. icon .. title .. ' ' | |
| -- Truncate if longer than max_width; otherwise let tabs be content-sized | |
| if max_width and wezterm.column_width(label) > max_width then | |
| while wezterm.column_width(label) > max_width - 1 do | |
| label = label:sub(1, -2) | |
| end | |
| label = label .. '…' | |
| end | |
| if color_key and tab_colors[color_key] then | |
| return wezterm.format({ | |
| { Attribute = { Intensity = 'Bold' } }, | |
| { Foreground = { Color = tab_colors[color_key] } }, | |
| { Text = label }, | |
| }) | |
| end | |
| return label | |
| end) | |
| -- ============================================================ | |
| -- Window title bar (top): workspace + git branch + time | |
| -- ============================================================ | |
| -- Helper: read git branch from .git/HEAD (no subprocess, no flashing windows) | |
| local function get_git_branch(cwd) | |
| if not cwd or cwd == '' then return '' end | |
| local dir = cwd | |
| for _ = 1, 20 do | |
| local f = io.open(dir .. '/.git/HEAD', 'r') | |
| if f then | |
| local head = f:read('*l') | |
| f:close() | |
| if head then | |
| return head:match('ref: refs/heads/(.+)') or head:sub(1, 8) | |
| end | |
| return '' | |
| end | |
| local parent = dir:match('(.+)[/\\][^/\\]+$') | |
| if not parent or parent == dir then return '' end | |
| dir = parent | |
| end | |
| return '' | |
| end | |
| -- Abbreviate cwd by replacing home dir with ~ | |
| local function abbreviate_cwd(cwd) | |
| if not cwd or cwd == '' then return '' end | |
| local home = wezterm.home_dir | |
| -- Normalize separators | |
| local norm_cwd = cwd:gsub('\\', '/') | |
| local norm_home = home:gsub('\\', '/') | |
| if norm_cwd:sub(1, #norm_home) == norm_home then | |
| return '~' .. norm_cwd:sub(#norm_home + 1) | |
| end | |
| return norm_cwd | |
| end | |
| -- Cached title state from update-status (where Pane methods work in mux mode) | |
| local cached_title_line = 'WezTerm' | |
| wezterm.on('update-status', function(window, pane) | |
| local workspace = '' | |
| pcall(function() workspace = window:active_workspace() or '' end) | |
| local cwd = '' | |
| pcall(function() | |
| local cwd_obj = pane:get_current_working_dir() | |
| if cwd_obj then | |
| cwd = cwd_obj.file_path or tostring(cwd_obj) | |
| cwd = cwd:gsub('^file://[^/]*', '') | |
| if is_windows then cwd = cwd:gsub('^/', '') end | |
| end | |
| end) | |
| local short_cwd = abbreviate_cwd(cwd) | |
| local branch = get_git_branch(cwd) | |
| local date_time = wezterm.strftime('%a %I:%M%p'):gsub(' 0', ' ') | |
| -- All status in the title bar (top): workspace | cwd | git | time | |
| local title_parts = {} | |
| if workspace ~= '' and workspace ~= 'default' then | |
| table.insert(title_parts, workspace) | |
| end | |
| if short_cwd ~= '' then table.insert(title_parts, short_cwd) end | |
| if branch ~= '' then table.insert(title_parts, 'git:' .. branch) end | |
| table.insert(title_parts, date_time) | |
| cached_title_line = table.concat(title_parts, ' | ') | |
| -- Clear left/right status since tabs are at the bottom and we don't | |
| -- need extra status in the tab bar | |
| window:set_left_status('') | |
| window:set_right_status('') | |
| end) | |
| wezterm.on('format-window-title', function(tab, pane, tabs, panes, conf) | |
| return cached_title_line | |
| end) | |
| -- ============================================================ | |
| -- F11: toggle title bar (cross-platform) | |
| -- ============================================================ | |
| wezterm.on('toggle-titlebar', function(window, pane) | |
| local overrides = window:get_config_overrides() or {} | |
| if overrides.window_decorations == 'RESIZE' then | |
| overrides.window_decorations = 'TITLE | RESIZE' | |
| else | |
| overrides.window_decorations = 'RESIZE' | |
| end | |
| window:set_config_overrides(overrides) | |
| end) | |
| -- ============================================================ | |
| -- Mouse bindings (cross-platform) | |
| -- ============================================================ | |
| config.mouse_bindings = { | |
| -- Select-to-copy | |
| { | |
| event = { Up = { streak = 1, button = 'Left' } }, | |
| mods = 'NONE', | |
| action = act.CompleteSelectionOrOpenLinkAtMouseCursor('ClipboardAndPrimarySelection'), | |
| }, | |
| -- Right-click paste | |
| { | |
| event = { Down = { streak = 1, button = 'Right' } }, | |
| mods = 'NONE', | |
| action = act.PasteFrom('Clipboard'), | |
| }, | |
| } | |
| -- ============================================================ | |
| -- Key bindings (cross-platform) | |
| -- ============================================================ | |
| config.keys = { | |
| -- Open launcher | |
| { key = 'n', mods = 'CTRL', action = act.ShowLauncherArgs { flags = 'FUZZY|LAUNCH_MENU_ITEMS|TABS' } }, | |
| -- Workspaces | |
| { key = 'd', mods = 'CTRL|SHIFT', action = act.EmitEvent 'spawn-dev-workspace' }, | |
| { key = 's', mods = 'CTRL|SHIFT', action = act.ShowLauncherArgs { flags = 'WORKSPACES' } }, | |
| -- Pane splits (Alt+Shift works on both platforms) | |
| { key = '\\', mods = 'ALT|SHIFT', action = act.SplitHorizontal { domain = 'CurrentPaneDomain' } }, | |
| { key = '-', mods = 'ALT|SHIFT', action = act.SplitVertical { domain = 'CurrentPaneDomain' } }, | |
| -- Tab navigation | |
| { key = 'LeftArrow', mods = 'CTRL|SHIFT', action = act.ActivateTabRelative(-1) }, | |
| { key = 'RightArrow', mods = 'CTRL|SHIFT', action = act.ActivateTabRelative(1) }, | |
| -- Tab reorder: direct binding (single press) OR enter move mode for repeat | |
| { key = 'LeftArrow', mods = 'CTRL|ALT', action = act.MoveTabRelative(-1) }, | |
| { key = 'RightArrow', mods = 'CTRL|ALT', action = act.MoveTabRelative(1) }, | |
| -- Ctrl+Alt+Up or Ctrl+Alt+Down: enter "move tab" mode — then plain | |
| -- Left/Right arrows repeat naturally. Escape/Enter exits. | |
| { key = 'UpArrow', mods = 'CTRL|ALT', | |
| action = act.ActivateKeyTable { name = 'move_tab', one_shot = false, timeout_milliseconds = 3000 } }, | |
| { key = 'DownArrow', mods = 'CTRL|ALT', | |
| action = act.ActivateKeyTable { name = 'move_tab', one_shot = false, timeout_milliseconds = 3000 } }, | |
| -- Toggle title bar | |
| { key = 'F11', mods = 'NONE', action = act.EmitEvent 'toggle-titlebar' }, | |
| -- Close tab / pane | |
| { key = 'q', mods = 'CTRL|ALT', action = act.CloseCurrentTab { confirm = false } }, | |
| { key = 'q', mods = 'CTRL|SHIFT', action = act.CloseCurrentPane { confirm = false } }, | |
| -- 1..9 jump to tab | |
| { key = '1', mods = 'CTRL', action = act.ActivateTab(0) }, | |
| { key = '2', mods = 'CTRL', action = act.ActivateTab(1) }, | |
| { key = '3', mods = 'CTRL', action = act.ActivateTab(2) }, | |
| { key = '4', mods = 'CTRL', action = act.ActivateTab(3) }, | |
| { key = '5', mods = 'CTRL', action = act.ActivateTab(4) }, | |
| { key = '6', mods = 'CTRL', action = act.ActivateTab(5) }, | |
| { key = '7', mods = 'CTRL', action = act.ActivateTab(6) }, | |
| { key = '8', mods = 'CTRL', action = act.ActivateTab(7) }, | |
| { key = '9', mods = 'CTRL', action = act.ActivateTab(8) }, | |
| } | |
| -- Key table for tab move mode: Ctrl+Shift+M enters, plain arrows move, | |
| -- Escape/Enter exits. Avoids the macOS Ctrl+Alt+Arrow non-repeat issue. | |
| config.key_tables = { | |
| move_tab = { | |
| { key = 'LeftArrow', action = act.MoveTabRelative(-1) }, | |
| { key = 'RightArrow', action = act.MoveTabRelative(1) }, | |
| { key = 'Escape', action = 'PopKeyTable' }, | |
| { key = 'Enter', action = 'PopKeyTable' }, | |
| }, | |
| } | |
| return config |
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
| # pwsh profile — emits OSC 7 on each prompt so WezTerm knows the current | |
| # working directory. Cross-platform: works on pwsh under Windows, macOS, Linux. | |
| # Without this, pane:get_current_working_dir() returns nil and the title bar | |
| # can't show the git branch or cwd. | |
| if ($env:WEZTERM_PANE) { | |
| $global:__WezTermOriginalPrompt = $function:prompt | |
| function global:prompt { | |
| $cwd = (Get-Location).Path -replace '\\', '/' | |
| # Cross-platform hostname: COMPUTERNAME on Windows, HOSTNAME/HOST on Unix | |
| $hostname = if ($env:COMPUTERNAME) { $env:COMPUTERNAME } | |
| elseif ($env:HOSTNAME) { $env:HOSTNAME } | |
| else { [System.Net.Dns]::GetHostName() } | |
| $esc = [char]27 | |
| $bel = [char]7 | |
| # OSC 7: ESC ] 7 ; file://HOST/PATH BEL | |
| $osc7 = $esc + ']7;file://' + $hostname + '/' + $cwd + $bel | |
| [Console]::Write($osc7) | |
| & $global:__WezTermOriginalPrompt | |
| } | |
| } |
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 | |
| # ssh-add-keys-unix.sh | |
| # Source from your shell profile (~/.bashrc, ~/.zshrc, etc.) to auto-load SSH keys. | |
| # Usage: Add this line to your profile: | |
| # source "$HOME/.config/ssh-add-keys-unix.sh" | |
| # | |
| # macOS: Uses Keychain to remember passphrases across reboots. | |
| # First-time setup (once per key, passphrase stored permanently): | |
| # ssh-add --apple-use-keychain ~/.ssh/your_key_ed25519 | |
| # Also add to ~/.ssh/config: | |
| # Host * | |
| # AddKeysToAgent yes | |
| # UseKeychain yes | |
| # | |
| # Linux: Keys loaded per session (passphrase required once per login). | |
| # Uses ssh-agent which should be started by your desktop environment or shell. | |
| # Bail if ssh-add isn't available | |
| command -v ssh-add >/dev/null 2>&1 || return 0 | |
| # Check if agent is running | |
| ssh-add -l >/dev/null 2>&1 | |
| agent_rc=$? | |
| # 0 = keys loaded, 1 = agent running but empty, 2 = agent not running | |
| [ "$agent_rc" -eq 2 ] && return 0 # no agent | |
| [ "$agent_rc" -eq 0 ] && return 0 # keys already loaded | |
| # Load all ed25519 private keys | |
| for key in "$HOME"/.ssh/*_ed25519; do | |
| [ -f "$key" ] || continue | |
| # Skip if it's a .pub file (glob shouldn't match, but be safe) | |
| case "$key" in *.pub) continue ;; esac | |
| if [ "$(uname)" = "Darwin" ]; then | |
| # macOS: use Keychain so passphrase is remembered across reboots | |
| ssh-add --apple-use-keychain "$key" 2>/dev/null | |
| else | |
| ssh-add "$key" 2>/dev/null | |
| fi | |
| done |
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
| # ssh-add-keys-windows.ps1 | |
| # Add to your PowerShell profile ($PROFILE) to auto-load SSH keys on login. | |
| # Usage: Add this line to $PROFILE: | |
| # . "$HOME\.config\ssh-add-keys-windows.ps1" | |
| # | |
| # Prerequisites: | |
| # - ssh-agent service enabled and running: | |
| # Set-Service -Name ssh-agent -StartupType Automatic | |
| # Start-Service ssh-agent | |
| # - Keys must have been added manually once first (to enter passphrases): | |
| # ssh-add $HOME\.ssh\your_key_ed25519 | |
| # Only run if ssh-agent is running and no keys are loaded | |
| $agentRunning = (Get-Service ssh-agent -ErrorAction SilentlyContinue).Status -eq 'Running' | |
| if (-not $agentRunning) { return } | |
| $loaded = & ssh-add -l 2>&1 | |
| if ($LASTEXITCODE -eq 0) { return } # keys already loaded | |
| # Load all ed25519 private keys (skips .pub files) | |
| Get-ChildItem "$HOME\.ssh\*_ed25519" -File | Where-Object { $_.Name -notlike '*.pub' } | ForEach-Object { | |
| ssh-add $_.FullName 2>$null | |
| } |
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
| # wezterm-osc7-unix.sh | |
| # Emits OSC 7 on every shell prompt so WezTerm knows the current | |
| # working directory. Works on bash and zsh (Mac/Linux). | |
| # | |
| # Source from your shell profile (~/.bashrc, ~/.zshrc): | |
| # [ -n "$WEZTERM_PANE" ] && source "$HOME/.config/wezterm-osc7-unix.sh" | |
| # Only activate inside WezTerm | |
| [ -n "$WEZTERM_PANE" ] || return 0 | |
| # Emit OSC 7: ESC ] 7 ; file://HOST/PATH BEL | |
| # WezTerm's parser is lenient about URL encoding, so we pass $PWD as-is. | |
| # Spaces and most special characters work fine. | |
| __wezterm_osc7_emit() { | |
| printf '\033]7;file://%s%s\a' "$(hostname)" "$PWD" | |
| } | |
| # Hook into the shell's prompt mechanism | |
| if [ -n "$ZSH_VERSION" ]; then | |
| # zsh: use precmd hook | |
| autoload -Uz add-zsh-hook 2>/dev/null | |
| if type add-zsh-hook >/dev/null 2>&1; then | |
| add-zsh-hook precmd __wezterm_osc7_emit | |
| else | |
| precmd_functions+=(__wezterm_osc7_emit) | |
| fi | |
| elif [ -n "$BASH_VERSION" ]; then | |
| # bash: prepend to PROMPT_COMMAND | |
| case "$PROMPT_COMMAND" in | |
| *__wezterm_osc7_emit*) ;; | |
| *) PROMPT_COMMAND="__wezterm_osc7_emit${PROMPT_COMMAND:+; $PROMPT_COMMAND}" ;; | |
| esac | |
| fi |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
WezTerm hotkeys & cheatsheet
Tabs
Ctrl+NCtrl+1…Ctrl+9Ctrl+Shift+Left/RightCtrl+Alt+Left/RightCtrl+Alt+Up/DownCtrl+Alt+QCtrl+Shift+QPanes
Alt+Shift+\Alt+Shift+-Workspaces
Ctrl+Shift+DCtrl+Shift+SAppearance / Config
F11Ctrl+=/Ctrl+-/Ctrl+0Ctrl+Shift+RBuilt-in utilities (WezTerm defaults)
Ctrl+Shift+FCtrl+Shift+SpaceCtrl+Shift+XCtrl+Shift+PMouse
Global (Windows only, via AutoHotkey script)
F12Launcher entries (what shows up in
Ctrl+N)All platforms share the same 9-slot structure: 2 shells + 2 agents + 5 SSH hosts.
docker compose execinto Codex container)docker compose execinto Claude container)Tab colors
Title bar / status
Title bar at the top shows:
workspace | ~/cwd | git:branch | Fri 10:30PMworkspace— only shown if not "default"~/cwd— abbreviated current working directorygit:branch— current git branch (reads.git/HEADdirectly, no subprocess)Fri 10:30PM— day of week + 12-hour time