If you've ever used whichkey in Neovim, you know the feeling: you press a key, a popup appears, and suddenly all your bindings are right there — discoverable, searchable, no memorization required.
I wanted that same experience in WezTerm. So I built wezterm-cmdpicker.
In short:
- Fuzzy-searchable command palette for all your WezTerm keybindings
- Auto-discovers bindings from your config — no re-definition needed
- Register action-only commands (SSH servers, Docker workflows, anything) without keybindings
- Color-coded layers: your registered bindings, config bindings, and WezTerm defaults
- Smart deduplication — overridden defaults are hidden automatically
The Problem
WezTerm is an incredibly powerful terminal emulator with a Lua-based configuration that lets you bind pretty much anything to a key combination. The downside? Once you have 20, 30, or 50+ keybindings, good luck remembering them all.
You end up in a loop:
- "What was the shortcut for splitting panes again?"
- Open your config file
- Scroll through a wall of keybindings
- Find it, go back to the terminal
- Forget it again next week
Sound familiar?
The Solution
wezterm-cmdpicker adds a command palette to WezTerm. Press one key combo (default: Leader + Space) and a fuzzy-searchable picker pops up showing all your keybindings — with descriptions, organized by source, and color-coded for clarity.
It works through a 3-layer discovery system:
- Registered bindings (yellow) — keybindings you explicitly register with descriptions through the plugin
-
Config bindings (cyan) — keybindings from your
config.keystable, auto-discovered without any extra work - Default bindings (grey) — WezTerm's built-in defaults, optionally included
The key idea: you don't have to re-define your existing keybindings. If they're already in config.keys, the plugin picks them up automatically. You only register explicitly when you want to add a human-readable description.
If you override a WezTerm default (say, rebinding Ctrl+Shift+T), only your version shows up in the picker. No duplicates, no confusion.
Getting Started
Here's a complete working example:
local wezterm = require('wezterm')
local act = wezterm.action
local config = wezterm.config_builder()
-- Load the plugin
local cmdpicker = wezterm.plugin.require(
'https://github.com/abidibo/wezterm-cmdpicker'
)
-- Register keybindings with descriptions
cmdpicker.add_keys(config, {
{
key = 'n',
mods = 'LEADER',
action = act.SpawnTab('CurrentPaneDomain'),
desc = 'New tab',
},
{
key = 'x',
mods = 'LEADER',
action = act.CloseCurrentPane({ confirm = true }),
desc = 'Close pane',
},
{
key = 'z',
mods = 'LEADER',
action = act.TogglePaneZoomState,
desc = 'Toggle zoom',
},
})
-- Any binding added directly to config.keys is also discovered
table.insert(config.keys, {
key = 'f',
mods = 'LEADER',
action = act.ToggleFullScreen,
})
-- Apply the picker (must be called LAST)
cmdpicker.apply_to_config(config, {
title = 'Command Palette',
})
return config
That's it. Press Leader + Space, start typing, and find any command instantly.
Real-World Use Cases
1. Separate Keybindings File
Many people keep keybindings in a separate file for organization. cmdpicker handles this cleanly:
-- keybindings.lua
local wezterm = require('wezterm')
local act = wezterm.action
return {
{
key = 'h',
mods = 'LEADER',
action = act.SplitHorizontal({ domain = 'CurrentPaneDomain' }),
desc = 'Split horizontal',
},
{
key = 'v',
mods = 'LEADER',
action = act.SplitVertical({ domain = 'CurrentPaneDomain' }),
desc = 'Split vertical',
},
}
-- wezterm.lua
config.keys = require('keybindings')
-- Single-arg form: registers for the picker
-- without modifying config.keys
cmdpicker.add_keys(config.keys)
cmdpicker.apply_to_config(config)
2. SSH Server Quick Access
This one is my favorite. You can register action-only commands — entries that appear in the picker without needing a keybinding:
local servers = {
{ name = 'Production', host = 'user@prod.example.com' },
{ name = 'Staging', host = 'user@staging.example.com' },
{ name = 'Dev', host = 'user@dev.example.com' },
}
for _, s in ipairs(servers) do
cmdpicker.register({
action = act.SpawnCommandInNewTab({
args = { 'ssh', s.host },
}),
desc = 'SSH: ' .. s.name .. ' (' .. s.host .. ')',
})
end
Now open the picker, type "ssh", and all your servers are right there. No keybinding needed, no separate SSH manager, just your terminal.
3. Project-Specific Workflows
Build quick-access commands for your daily workflows:
cmdpicker.register({
{
action = act.SpawnCommandInNewTab({
args = { 'docker', 'compose', 'up', '-d' },
}),
desc = 'Docker: Start services',
},
{
action = act.SpawnCommandInNewTab({
args = { 'docker', 'compose', 'logs', '-f' },
}),
desc = 'Docker: Follow logs',
},
{
action = act.SpawnCommandInNewTab({
args = { 'lazygit' },
}),
desc = 'Open lazygit',
},
})
Configuration Options
The apply_to_config function accepts several options to customize the picker:
cmdpicker.apply_to_config(config, {
key = ' ', -- Trigger key (default: Space)
mods = 'LEADER', -- Trigger modifiers (default: LEADER)
title = 'Command Palette', -- Picker window title
include_defaults = true, -- Include WezTerm defaults
include_key_tables = false, -- Include copy_mode, search_mode, etc.
fuzzy = true, -- Enable fuzzy search
fuzzy_description = 'Search: ', -- Search prompt text
})
Try It Out
If you're not on WezTerm yet — it's a GPU-accelerated, cross-platform terminal emulator configured entirely in Lua, with a built-in plugin system. With cmdpicker, it gets the kind of command palette you know from VS Code — right in your terminal.
The plugin is open source and takes one line to install:
GitHub: github.com/abidibo/wezterm-cmdpicker
If it saves you a trip to your config file, drop a star on the repo. And if you have ideas or find bugs, issues and PRs are welcome!

Top comments (0)