2026-10-08 11:46:36 -04:00
|
|
|
#!/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
|
|
|
|
|
#
|
2026-10-08 11:53:56 -04:00
|
|
|
# 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; when it
|
|
|
|
|
# exits the pane keeps a shell (with the output above it) instead of closing.
|
2026-10-08 11:46:36 -04:00
|
|
|
|
|
|
|
|
set -uo pipefail
|
|
|
|
|
self=$(readlink -f "$0")
|
|
|
|
|
|
|
|
|
|
finish() {
|
|
|
|
|
local status=$1
|
|
|
|
|
printf '\n\033[2m[exit %s]\033[0m\n' "$status"
|
|
|
|
|
exec "${SHELL:-bash}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-08 11:53:56 -04:00
|
|
|
# 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() {
|
2026-10-08 11:46:36 -04:00
|
|
|
local dir=$1
|
|
|
|
|
shift
|
|
|
|
|
local q
|
|
|
|
|
q=$(printf '%q ' "$@")
|
2026-10-08 11:53:56 -04:00
|
|
|
printf 'split-window -v -f -l 25%% -c %q %s' "$dir" "$q"
|
2026-10-08 11:46:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2026-10-08 11:53:56 -04:00
|
|
|
item Quick "$icon" "$title" "in ${dir/#$HOME/~}" "$aliases" "$(split_action "$dir" "$self" --cmd "$dir" "$@")"
|
2026-10-08 11:46:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ── 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" "" \
|
2026-10-08 11:53:56 -04:00
|
|
|
"$(split_action "$troot" "$self" --run "$kind" "$target" "$troot" "$runner")"
|
2026-10-08 11:46:36 -04:00
|
|
|
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 .
|