Skip to content

Instantly share code, notes, and snippets.

@romgenie
Last active April 13, 2026 14:10
Show Gist options
  • Select an option

  • Save romgenie/a6acd389dc53c78802b971058cd281f1 to your computer and use it in GitHub Desktop.

Select an option

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
-- ~/.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
# 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
}
}
#!/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
# 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
}
# 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
@romgenie
Copy link
Copy Markdown
Author

romgenie commented Apr 11, 2026

WezTerm hotkeys & cheatsheet

Tabs

Key Action
Ctrl+N Open fuzzy launcher (shells, agents, SSH hosts)
Ctrl+1Ctrl+9 Jump to tab 1–9
Ctrl+Shift+Left / Right Switch to previous / next tab
Ctrl+Alt+Left / Right Move current tab one position left / right
Ctrl+Alt+Up / Down Enter "move tab" mode (then plain arrows repeat; Esc to exit)
Ctrl+Alt+Q Close current tab (no confirm)
Ctrl+Shift+Q Close current pane (no confirm; closes tab if only 1 pane)

Panes

Key Action
Alt+Shift+\ Split pane horizontally
Alt+Shift+- Split pane vertically

Workspaces

Key Action
Ctrl+Shift+D Spawn "dev" workspace (multi-pane recipe)
Ctrl+Shift+S Show workspace switcher

Appearance / Config

Key Action
F11 Toggle OS title bar on/off
Ctrl+= / Ctrl+- / Ctrl+0 Font size up / down / reset (built-in)
Ctrl+Shift+R Reload config (hot reload; some changes need full restart)

Built-in utilities (WezTerm defaults)

Key Action
Ctrl+Shift+F Search scrollback
Ctrl+Shift+Space Quick-select mode (keyboard pick URLs / hashes on screen)
Ctrl+Shift+X Copy mode (vim-style scrollback navigation)
Ctrl+Shift+P Command palette

Mouse

Action Result
Left-click drag Select text (auto-copies on release)
Right-click Paste from clipboard

Global (Windows only, via AutoHotkey script)

Key Action
F12 Quake mode — show/hide WezTerm from anywhere in Windows

Launcher entries (what shows up in Ctrl+N)

All platforms share the same 9-slot structure: 2 shells + 2 agents + 5 SSH hosts.

# Windows macOS Linux
1 PowerShell zsh bash
2 Command Prompt bash zsh
3 Codex (full access) Codex (via docker compose exec into Codex container) Codex (full access)
4 Claude Code (skip permissions, high effort) Claude Code (via docker compose exec into Claude container) Claude Code (skip permissions, high effort)
5 SSH: complete.tech (teal) same same
6 SSH: steamanddip.com (cyan) same same
7 SSH: aiascension.tech (violet) same same
8 SSH: agentonomics.app (green) same same
9 SSH: webhook.complete.tech (red, auto-cd to ~/webhook) same same

Tab colors

Tab Color
PowerShell / Windows PowerShell Red
Command Prompt Blue
zsh Green
bash Yellow
Codex White
Claude Code Terracotta/orange
complete.tech Teal
steamanddip.com Cyan
aiascension.tech Violet
agentonomics.app Green
webhook.complete.tech Red/pink

Title bar / status

Title bar at the top shows: workspace | ~/cwd | git:branch | Fri 10:30PM

  • workspace — only shown if not "default"
  • ~/cwd — abbreviated current working directory
  • git:branch — current git branch (reads .git/HEAD directly, no subprocess)
  • Fri 10:30PM — day of week + 12-hour time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment