cdx is a minimal, extensible wrapper around cd that dispatches lifecycle hooks when you enter a directory. It supports synchronous and asynchronous hooks, per-directory config via .cdxrc, and --up / -N shorthand for climbing parent directories. A pluggable resolver chain (zoxide, zsh-z, z, z.lua, autojump) resolves fuzzy targets before falling back to a normal cd.
Use the install script (it downloads the core file, default hooks, and completions):
curl -fsSL https://raw.githubusercontent.com/monkeymonk/cdx/main/install.sh | bashWhat the installer does:
- Installs
cdx.shto~/.local/bin/cdx.sh. - Creates
~/.config/cdx/withconfig.shandhooks/(if missing). - Adds
source ~/.local/bin/cdx.shto your shell rc file. - Installs completions to
~/.local/bin/completions/(and to standard bash-completion dir if available).
To uninstall, remove the source line from your rc file and delete ~/.local/bin/cdx.sh and ~/.config/cdx/.
Zsh: Completions are automatically registered when cdx.sh is sourced in an interactive zsh session. It locates completions/cdx.zsh relative to the script (or via $fpath) and registers completions for cdx — and for cd too if it's aliased to cdx. No manual setup needed.
Bash: The installer places the completion file in ~/.local/share/bash-completion/completions/. If you install manually, source it from your rc:
source /path/to/cdx/completions/cdx.bashLoad cdx into your shell (installer already does this):
source ~/.local/bin/cdx.shBasic usage:
cdx /path/to/project
cdx # go to $HOME
cdx -i /path # inspect mode (no directory change)
cdx --help # show help
cdx --version # show version
cdx -- /path # stop flag parsing (treat next arg as path)Go up parent directories with --up or the -N shorthand:
cdx --up # go up 1 level
cdx --up 3 # go up 3 levels
cdx --up 2/src # go up 2 levels, then into src/
cdx -1 # shorthand: up 1 level
cdx -3 # shorthand: up 3 levels
cdx -2/src # shorthand: up 2 levels, then into src/
cdx -i --up 2 # inspect mode, up 2 levelsRecommended shell aliases:
alias ..='cdx --up'
alias ...='cdx --up 2'
alias ....='cdx --up 3'cdx:
-i: inspect mode (do not change directories; hooks still run).-h,--help: show help.-v,--version: show version.--: end of options; treat the next argument as a literal path.PATH: optional target path; defaults to$HOME.
cdx --up / cdx -N:
--up [N[/subpath]]: go up N parent levels (default 1), optionally into subpath.-N[/subpath]: shorthand, e.g.cdx -2/src.-i: inspect mode.
Hooks are shell functions registered via cdx_register_hook and are called on each navigation with two arguments:
cdx_hook_name() {
local mode="$1" # enter | inspect
local dir="$2" # absolute target path
}Hook types:
sync: runs in order, blocks navigation.async: runs in the background, fire-and-forget.
By default, hooks only run in interactive shells. You can control this with an optional third argument to cdx_register_hook:
| Context | Interactive shell | Non-interactive shell (CI, editors, scripts) |
|---|---|---|
interactive |
yes | — |
noninteractive |
— | yes |
all |
yes | yes |
cdx_register_hook sync cdx_hook_preview # interactive only (default)
cdx_register_hook sync cdx_hook_env all # both contexts
cdx_register_hook async cdx_hook_log noninteractive # non-interactive onlyThis prevents hooks like preview or git from producing unwanted output in non-interactive shells (e.g. when cd is aliased to cdx inside CI runners or editor subprocesses).
# ~/.config/cdx/hooks/hello.sh
cdx_hook_hello() {
local mode="$1" dir="$2"
[[ "$mode" = "enter" ]] || return 0
echo "Hello from $dir"
}
cdx_register_hook sync cdx_hook_helloEnable it in config.sh:
CDX_HOOKS_ENABLED=(preview git hello)Built-in hooks (shipped by the installer):
preview(sync): lists directory contents (useseza,exa, orls).git(sync): showsgit status -sbwhen.git/is present.notify(async): desktop notification vianotify-send.docker(async): switches Docker context from.docker-context.
All user configuration lives under ~/.config/cdx/ (or $XDG_CONFIG_HOME/cdx):
config.sh: global config sourced at shell startup.hooks/: hook scripts (one file per hook name).
Example config.sh:
# ~/.config/cdx/config.sh
CDX_HOOKS_ENABLED=(preview git)CDX_CONFIG_DIR: override config root (defaults to~/.config/cdx).CDX_HOOKS_ENABLED: list of hook names to load (e.g.,preview git notify).CDX_RESOLVERS: override resolver order (e.g.,CDX_RESOLVERS=(zoxide z)). By default, cdx auto-detects installed resolvers from: zoxide, zsh-z, z, z.lua, autojump.CDX_CDXRC: set to0to disable per-directory.cdxrcsourcing.CDX_LS_ARGS: arguments passed toeza/exa/lsin the preview hook.
Per-directory config via .cdxrc:
# /path/to/project/.cdxrc
CDX_HOOKS_ENABLED=(preview git docker).cdxrc is sourced after resolving the target directory and before hook dispatch. This allows per-project behavior, such as enabling additional hooks or setting hook-specific environment variables.
Run tests with the bundled Bats runner:
./tests/bats/bin/bats tests/ tests/hooks/cdxf() {
local dir
dir="$(find . -type d 2>/dev/null | fzf)"
[[ -n "$dir" ]] && cdx "$dir"
}