I have a few posts about just already: the basic command runner, the global justfile, and tmux window names with just. The wrapper in my .zshrc rolls all three behaviors into one function so I do not have to think about them.
What the wrapper does
If a recipe is not in the project justfile but exists in ~/.config/just/justfile, the wrapper runs the global one with --working-directory $PWD, so it acts on the current directory. That way I can keep things like clean-docker or my-ip in one place and still call them from any project.
Before a recipe runs, the wrapper renames the tmux window to the recipe name. When it exits, the window goes back to zsh. Outside of tmux this part does nothing.
The third piece is --choose. Project and global recipes show up in one fuzzy picker. Globals are dimmed and prefixed with ∘. If both files have a recipe with the same name, the project one wins.
The CLI is the same as plain just, so teammates who do not source the function get plain just and nothing breaks.
Why a wrapper, not a justfile change
I first tried doing the tmux rename inside each project justfile, with helper recipes that call tmux rename-window. It works, but it adds the same six lines to every project, and every recipe has to remember to call the restore. A wrapper does it once in my shell config, and project justfiles stay clean for teammates.
The global fallback is the same logic. I want my personal recipes everywhere, but I do not want to teach each project about them.
The code
# `just` wrapper — adds three things on top of plain `just`:# 1. Falls back to the global justfile (~/.config/just/justfile) when the# project doesn't define a recipe.# 2. Renames the tmux window to the recipe name while it runs.# 3. `--choose` shows project + global recipes in one fuzzy picker.# Project justfiles are untouched; teammates without this function get plain# `just`. Keep it identical to `just`'s CLI surface.just() { local global_justfile="${XDG_CONFIG_HOME:-$HOME/.config}/just/justfile"
# Detect whether $PWD (or any ancestor) has a project justfile. local in_project=0 command just --show default &>/dev/null && in_project=1 if (( ! in_project )); then command just --summary &>/dev/null && in_project=1 fi
# `--choose`: merged fuzzy picker over project + global recipes if [[ "$1" == "--choose" ]]; then local chooser="${JUST_CHOOSER:-fzf --ansi}" local project_list="" global_list="" selected
if (( in_project )); then project_list=$(command just --summary 2>/dev/null | tr ' ' '\n' | grep -v '^$') fi if [[ -f "$global_justfile" ]]; then global_list=$(command just -g --summary 2>/dev/null | tr ' ' '\n' | grep -v '^$') fi
selected=$( { [[ -n "$project_list" ]] && while IFS= read -r r; do printf ' %s\n' "$r" done <<< "$project_list" while IFS= read -r r; do [[ -z "$r" ]] && continue if [[ -z "$project_list" ]] || ! echo "$project_list" | grep -qxF "$r"; then printf '\033[2m∘\033[0m %s\n' "$r" fi done <<< "$global_list" } | eval "$chooser" | awk '{print $NF}' ) || return [[ -z "$selected" ]] && return
just "$selected" return fi
# First non-flag argument is the recipe name. local recipe="" for arg in "$@"; do [[ "$arg" == -* ]] && continue recipe="$arg" break done
# `--list` / `-l`: show project recipes (if any) followed by globals. if [[ "$1" == "--list" || "$1" == "-l" ]]; then if (( in_project )); then command just "$@" fi if [[ -f "$global_justfile" ]] && command just -g --list &>/dev/null; then (( in_project )) && echo "" echo "Global recipes:" command just -g "$@" fi return fi
# Bare `just` outside any project: show globals instead of erroring. if [[ $# -eq 0 ]] && (( ! in_project )) && [[ -f "$global_justfile" ]]; then command just -g return fi
# Recipe missing in project but present globally: run the global one against $PWD. local -a prefix=(command just) if [[ -n "$recipe" ]] \ && [[ -f "$global_justfile" ]] \ && ! command just --show "$recipe" &>/dev/null \ && command just --justfile "$global_justfile" --show "$recipe" &>/dev/null; then prefix=(command just --justfile "$global_justfile" --working-directory "$PWD") fi
# Rename the tmux window to the recipe while it runs, then back to "zsh". if [[ -n "$TMUX" ]] && [[ -n "$recipe" ]]; then tmux rename-window "$recipe" "${prefix[@]}" "$@" local exit_code=$? tmux rename-window "zsh" return $exit_code fi
"${prefix[@]}" "$@"}
alias j="just --choose"