A just wrapper for tmux and global recipes

· Tech

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

Terminal window
# `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"