confhelp
Find and edit your keybindings instantly.
The Problem
Over time you accumulate tmux bindings, zsh aliases, vim mappings, custom functions - scattered across dozens of files. You know you set something up, but where?
Two pain points:
- Finding bindings - "What key did I bind for git status?" "Do I have an alias for docker compose?"
- Editing bindings - You remember it exists, now you need to change it. Which file? What line? grep → open → scroll → find → edit. Every. Single. Time.
The Solution
confhelp -b ~/dotfiles --edit
Fuzzy search all your bindings → select one → opens $EDITOR at exact file:line.
Install
pip install confhelp
Usage
# Output all bindings (uses base_dirs from config)
confhelp
# Interactive fzf selection
confhelp --select
# Select and open in $EDITOR at line
confhelp --edit
# JSON output
confhelp -f json
# Override base directory
confhelp -b ~/other-dotfiles
# Show keys defined more than once
confhelp --conflicts
# Report lines that look like bindings but failed to parse, plus any runtime
# bindings a query engine had to drop for lack of a source location
confhelp --check
Example output:
[tmux] prefix+g display-popup -w 80%... .tmux.conf:42
[alias] gs git status .zsh_aliases:15
[bind] ^[e edit-command-line .zshrc:89
The --edit flag drops you directly into the file at the exact line. Change the binding, save, done.
Binding Sources
confhelp supports two ways to extract bindings:
1. Regex Parsing (data-driven)
Define patterns in TOML to extract bindings from config files. Works for any text-based config format - tmux, zsh, aliases, etc. You specify paths, regex, and capture groups.
2. Structured Parsing (data-driven)
For configs that store bindings in structured formats (TOML arrays, YAML lists, JSON), use the structured parser. It navigates the parsed data directly - no regex needed.
[zledit-actions]
parser = "toml"
paths = ["~/.config/zledit/config.toml"]
binding_path = "actions"
key = "binding"
desc = "description"
type = "zledit"
Parses configs like:
[[actions]]
binding = 'ctrl-o'
description = 'edit'
script = '~/.config/zledit/scripts/edit.sh'
3. Query Engines (code-driven)
Some tools (like nvim) store bindings in ways that can't be reliably parsed with regex - runtime keymaps, plugin-generated bindings, multi-line table formats. Query engines run the tool itself to extract bindings.
Currently available: nvim (runs nvim headlessly to query keymaps) and tmuxinator (reads session files).
The nvim engine needs a literal desc at the call site. nvim reports no source
location for a Lua keymap: lnum is 0 and sid collapses to init.lua for anything
loaded through require. So the engine recovers a location by grepping your config
for the description as written, and a binding it cannot place is left out, since there
would be nothing for --edit to jump to. That filter is what keeps several hundred
plugin bindings out of the listing, but it also hides your own binding if its desc
reached nvim through a variable:
-- invisible to confhelp: the literal never appears in a file
local set = function(lhs, rhs, desc)
vim.keymap.set("n", lhs, rhs, { desc = desc })
end
set("<leader>ff", cmd, "Find files")
-- visible: the call site spells the description out
set("<leader>ff", cmd, { desc = "Find files" })
confhelp --check reports how many runtime bindings were dropped this way. Plugin
bindings are expected in that list; yours are not. To see only your own:
confhelp --check 2>/dev/null | grep '^\[nvim\] <leader>'
Descriptions should be unique. The index is keyed by description text, so two bindings sharing one resolve to the same location (the first in sorted file order).
Architecture note: Query engines are currently hardcoded. Future versions may support registering custom extractors - any code that returns (type, key, desc, file, line) tuples could become a binding source. This would allow community-contributed engines for tools like vim, emacs, i3, sway, etc.
Config Format
Define parsers in TOML. Each section describes how to extract bindings from a set of files:
# Default directories to search (no -b needed)
base_dirs = ["~/dotfiles", "~/work-dotfiles"]
# Query engines - run external tools to get bindings
# Available: "nvim" (queries nvim headlessly for keymaps)
query_engines = ["nvim"]
# tmux: bind [-n] <key> <command>
# Example: bind r source-file ~/.tmux.conf
# bind-key -n M-l popup -E lazygit
# Captures: (1) key=r or M-l, (2) command
[tmux]
paths = [".tmux.conf"]
match_line = "^bind"
regex = 'bind(?:-key)?\s+(?:-n\s+)?(\S+)(.*)'
key_group = 1
desc_group = 2
type = "tmux"
truncate = 100
# zsh alias: alias [-gs] <name>=<command>
# Example: alias gs='git status'
# alias -g C='| xsel --clipboard'
# Captures: (1) name=gs or C, (2) command
[alias]
paths = [".zsh_aliases", ".zsh_claude"]
regex = "alias\\s+(?:-[gs]\\s+)?([^=]+)=(.*)"
key_group = 1
desc_group = 2
type = "alias"
strip_quotes = true
# zsh-abbr: "abbrev" 'expansion'
# Example: "ga" 'git add'
# Captures: (1) abbrev=ga, (2) expansion=git add
[abbrev]
paths = [".zsh_abbreviations"]
match_line = '".*"'
regex = '''"([^"]+)"\s+'([^']+)''''
key_group = 1
desc_group = 2
type = "abbrev"
# nvim query engine options
[engine.nvim]
truncate = 60
path = ".config/nvim" # where to grep for desc literals, relative to base_dir
# tmuxinator query engine options
[engine.tmuxinator]
path = "~/.config/tmuxinator"
# nvim via regex (alternative to query engine - parses source files directly)
# Only catches single-line vim.keymap.set calls with inline desc
[nvim-regex]
paths = [".config/nvim/lua/**/*.lua"]
match_line = "vim.keymap.set"
regex = 'vim\.keymap\.set\([^,]+,\s*"([^"]+)".*desc\s*=\s*"([^"]+)"'
key_group = 1
desc_group = 2
type = "nvim"
Config Options
Top-level options:
| Option | Description |
|---|---|
base_dirs |
Default directories to search |
query_engines |
List of query engines to enable (e.g., ["nvim"]) |
Section options (regex parsing):
| Option | Description |
|---|---|
paths |
List of files or glob patterns (e.g., **/*.lua) |
regex |
Pattern with capture groups for key/desc (Python re syntax) |
key_group |
Capture group number for the key |
desc_group |
Capture group number for description |
match_line |
Only process lines matching this pattern |
skip_comment |
Skip lines starting with # |
truncate |
Max length for description |
strip_quotes |
Remove surrounding quotes from desc |
desc_literal |
Use fixed string as description |
desc_from_comment |
Extract desc from trailing # comment |
Section options (structured parsing):
| Option | Description |
|---|---|
parser |
Format: toml, yaml, or json |
paths |
List of config files to parse |
binding_path |
Dot notation path to bindings array (e.g., bindings.keys) |
key |
Field name for binding key (supports field1+field2 to combine) |
desc |
Field name for description |
type |
Binding type label |
truncate |
Max length for description |
Regex Tips
Patterns use Python's re module. Test patterns at regex101.com (select Python flavor).
Quick CLI test:
echo "bind r reload" | python -c "import re,sys; m=re.search(r'bind\s+(\S+)\s+(.*)', sys.stdin.read()); print(m.groups() if m else 'no match')"
Output Formats
pipe(default): pipe-delimited[type]|key|desc|file:linetsv: Tab-separatedjson: JSON array
The pipe format works well with column -t -s'|' for aligned display.
Integration Examples
confhelp outputs text. How you display it is up to you.
fzf with Multiple Actions
Use --expect to handle different keys. Ctrl+P extracts and opens paths from entries:
result=$(confhelp -b ~/dotfiles | column -t -s'|' | fzf \
--header='Enter=edit | Ctrl-P=open path | Ctrl-O=copy' \
--expect=ctrl-p,ctrl-o)
key=$(echo "$result" | head -1)
selection=$(echo "$result" | tail -1)
case "$key" in
ctrl-p)
# Extract path from selection (supports /, ~, $HOME prefixes)
path=$(echo "$selection" | grep -oE '(/[^ ]+|~[^ ]+|\$HOME[^ ]+)' | head -1)
path="${path/#\~/$HOME}"
[[ -e "$path" ]] && $EDITOR "$path"
;;
ctrl-o)
echo "$selection" | xsel -ib
;;
*)
# Enter: parse file:line and open in editor
file_line=$(echo "$selection" | awk '{print $NF}')
file="${file_line%:*}"
line="${file_line##*:}"
$EDITOR "+$line" "$file"
;;
esac
Alacritty Popup
Spawn a centered popup window:
alacritty --class popup -e bash -c 'confhelp -b ~/dotfiles --edit'
See examples/alacritty-popup.sh for a complete implementation with Ctrl+P path support.
tmux Popup
tmux display-popup -w 80% -h 80% -E 'confhelp -b ~/dotfiles --select'
Rofi/dmenu
confhelp -b ~/dotfiles | rofi -dmenu
Acknowledgments
Inspired by Extracto.
License
MIT
Metadata
Release files for confhelp 0.8.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| confhelp-0.8.0.tar.gz | 284.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| confhelp-0.8.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 300.0 kB
Release files / confhelp-0.8.0.tar.gz
| Download URL | confhelp-0.8.0.tar.gz |
|---|---|
| Size | 284.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ba5f58e3142d3037e3f634eb31e31582a4242c06eb4251ea48a1815c006f6190
|
|
BLAKE2b-256 checksum How to use checksums |
f9e04a3e909afe1696adcb372fdadf7c29cce7efeed4a6914bdae77630ac9168
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency logRelease files / confhelp-0.8.0-py3-none-any.whl
| Download URL | confhelp-0.8.0-py3-none-any.whl |
|---|---|
| Size | 15.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ef9acef4cc0c1d41f4c772acc5cb003e7463fbceceaacc2dbd8fe40a663caaaf
|
|
BLAKE2b-256 checksum How to use checksums |
b3b5a349ff09c1eb8f11c48b6c7134ec600e36afdd3f6f17070ce6ad3f15b883
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency log