#!/usr/bin/env bash # pj-palette — pj's task runner as a tmux-palette source (the "tasks" palette, # ~/.config/tmux-palette/palettes/tasks.json). # # pj-palette print palette items (JSON) for the active pane's # project: quick actions (install, dev, sync, make, # …) and every task `pj --list` finds # pj-palette --run KIND TARGET ROOT RUNNER # run a pj task (the item's action) # pj-palette --cmd ROOT CMD… # run a quick-action command in ROOT # # Every item opens a full-width split along the bottom of the window (25% of # its height) in the project root. The command runs in the foreground; ctrl-c # stops it (the output stays). Once it has exited, enter or ctrl-c closes the # pane. set -uo pipefail self=$(readlink -f "$0") # While the task runs, ctrl-c reaches only the task: with SIGINT trapped, # bash survives the child's death instead of exiting along with it. trap : INT finish() { local status=$1 key printf '\n\033[2m[exit %s] enter or ctrl-c to close\033[0m' "$status" trap 'exit 0' INT while IFS= read -rsn1 key; do [ -z "$key" ] && exit 0 # enter done exit 0 } case ${1-} in --run) shift pj --run "$@" finish $? ;; --cmd) cd "$2" || exit 1 shift 2 printf '→ %s\n' "$*" >&2 "$@" finish $? ;; esac have() { command -v "$1" >/dev/null 2>&1; } cwd=$(tmux display -p '#{pane_current_path}' 2>/dev/null) || cwd=$PWD cd "$cwd" || exit 1 root=$(git rev-parse --show-toplevel 2>/dev/null) || root=$cwd icon_for() { case $1 in bun) printf '' ;; npm | yarn) printf '' ;; pnpm) printf '' ;; make) printf '' ;; go) printf '' ;; cargo) printf '' ;; uv | py | poe | pdm | rye | taskipy | hatch | script) printf '' ;; *) printf '' ;; esac } # tmux action that opens a full-width bottom split (25%) in DIR running ARGV # directly (no shell, so nothing is re-parsed by fish). The palette wrapper # evals the string in bash, hence %q. split_action() { local dir=$1 shift local q q=$(printf '%q ' "$@") printf 'split-window -v -f -l 25%% -c %q %s' "$dir" "$q" } items=() item() { # category icon title description aliases(csv) tmux-action items+=("$(jq -cn --arg c "$1" --arg i "$2" --arg t "$3" --arg d "$4" --arg a "$5" --arg x "$6" \ '{category: $c, icon: $i, title: $t, description: $d, aliases: ($a | split(",") | map(select(. != ""))), action: {tmux: $x}}')") } quick() { # icon title aliases dir argv… local icon=$1 title=$2 aliases=$3 dir=$4 shift 4 item Quick "$icon" "$title" "in ${dir/#$HOME/~}" "$aliases" "$(split_action "$dir" "$self" --cmd "$dir" "$@")" } # ── Quick actions, from the files at the project root ───────────────────── if [ -f "$root/package.json" ]; then pm=$(jq -r '.packageManager // empty' "$root/package.json" 2>/dev/null) pm=${pm%%@*} if [ -z "$pm" ]; then if [ -f "$root/bun.lock" ] || [ -f "$root/bun.lockb" ]; then pm=bun elif [ -f "$root/pnpm-lock.yaml" ]; then pm=pnpm elif [ -f "$root/yarn.lock" ]; then pm=yarn else pm=npm fi fi if have "$pm"; then quick "$(icon_for "$pm")" "$pm install" "install,i" "$root" "$pm" install jq -e '.scripts.dev' "$root/package.json" >/dev/null 2>&1 && quick "$(icon_for "$pm")" "$pm run dev" "dev" "$root" "$pm" run dev fi fi if [ -f "$root/pyproject.toml" ] && have uv; then quick "$(icon_for uv)" "uv sync" "sync,install" "$root" uv sync quick "$(icon_for uv)" "uv run python" "py,repl" "$root" uv run python fi if have make && { [ -f "$root/Makefile" ] || [ -f "$root/makefile" ] || [ -f "$root/GNUmakefile" ]; }; then quick "$(icon_for make)" "make" "m,build" "$root" make fi [ -f "$root/go.mod" ] && have go && quick "$(icon_for go)" "go run ." "run" "$root" go run . [ -f "$root/Cargo.toml" ] && have cargo && quick "$(icon_for cargo)" "cargo run" "run" "$root" cargo run # ── Every task pj finds (npm/bun/pnpm scripts, make, just, mise, devbox, # pyproject task tables and console scripts) ───────────────────────────── while IFS=$'\t' read -r display kind target troot runner _file; do [ -n "$kind" ] || continue tag=${display%% *} desc="$runner $target" [ "$troot" != "$root" ] && desc+=" (${troot#"$root"/})" item "Tasks: $tag" "$(icon_for "$tag")" "$target" "$desc" "" \ "$(split_action "$troot" "$self" --run "$kind" "$target" "$troot" "$runner")" done < <(PJ_QUIET=1 pj --list 2>/dev/null) if [ ${#items[@]} -eq 0 ]; then item Tasks '' "No tasks found" "in ${cwd/#$HOME/~}" "" "display-message 'pj: no task sources here'" fi printf '%s\n' "${items[@]}" | jq -s .