126 lines
6.5 KiB
Bash
Executable File
126 lines
6.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# workmux-palette — workmux as a tmux-palette source (the "workmux" palette,
|
|
# ~/.config/tmux-palette/palettes/workmux.json).
|
|
#
|
|
# workmux-palette print palette items (JSON): every agent
|
|
# workmux tracks in this tmux server (waiting,
|
|
# then done, then working), the worktrees of the
|
|
# active pane's repo with Close/Merge/Remove
|
|
# items, New from a prompt, and the dashboard,
|
|
# sidebar and last-done tools
|
|
# workmux-palette --add BRANCH the palette's query item (what was typed):
|
|
# `workmux add BRANCH` in the active pane's repo
|
|
# workmux-palette --prompt the New item's popup body: `gum write` the
|
|
# task (enter submits, ctrl+j newline, esc
|
|
# cancels), then `workmux add -A -p TASK`
|
|
# workmux-palette --popup DIR CMD…
|
|
# run CMD… in DIR in a popup that closes when it
|
|
# succeeds and waits for a key when it fails
|
|
# workmux-palette --do DIR CMD…
|
|
# run CMD… in DIR; a failure shows its last
|
|
# output line as a tmux message
|
|
#
|
|
# Lifecycle commands (add, merge, remove) run in a popup: they print hook
|
|
# output and may ask y/n (remove with unmerged commits). Open and close are
|
|
# quick, so they run in place.
|
|
|
|
set -uo pipefail
|
|
self=$(readlink -f "$0")
|
|
|
|
case ${1-} in
|
|
--exec) # the popup's command (see --popup)
|
|
shift
|
|
"$@"
|
|
status=$?
|
|
[ "$status" -eq 0 ] && exit 0
|
|
printf '\n\033[2m[exit %s] any key to close\033[0m' "$status"
|
|
read -rsn1
|
|
exit 0
|
|
;;
|
|
--popup)
|
|
dir=$2
|
|
shift 2
|
|
# argv form: tmux runs it without a shell and expands no formats in it.
|
|
title=${*/#"$self"/${self##*/}}
|
|
exec tmux display-popup -E -w 80% -h 60% -d "$dir" -T " ${title//#/##} " "$self" --exec "$@"
|
|
;;
|
|
--do)
|
|
cd "$2" || exit 1
|
|
shift 2
|
|
if ! out=$("$@" 2>&1); then
|
|
msg=$(tail -n1 <<<"$out")
|
|
tmux display-message -d 5000 "workmux: ${msg//#/##}"
|
|
fi
|
|
exit 0
|
|
;;
|
|
--add)
|
|
cwd=$(tmux display -p '#{pane_current_path}')
|
|
exec "$self" --popup "$cwd" workmux add "$2"
|
|
;;
|
|
--prompt)
|
|
# The branch name comes from omp under auto_name in workmux's config.yaml.
|
|
task=$(gum write --width 0 --height 8 \
|
|
--header "New worktree in ${PWD##*/}: describe the task (enter submits, ctrl+j newline, esc cancels)" \
|
|
--placeholder "What should the agent do?") || exit 0
|
|
[ -n "${task//[[:space:]]/}" ] || exit 0
|
|
printf '→ naming the branch…\n'
|
|
exec workmux add -A -p "$task"
|
|
;;
|
|
esac
|
|
|
|
cwd=$(tmux display -p '#{pane_current_path}' 2>/dev/null) || cwd=$PWD
|
|
root=$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null) || root=
|
|
worktrees='[]'
|
|
[ -n "$root" ] && worktrees=$(cd "$root" && workmux list --json 2>/dev/null) || worktrees='[]'
|
|
|
|
workmux status --all --json 2>/dev/null | jq --arg self "$self" --arg home "$HOME" --arg root "$root" \
|
|
--argjson worktrees "${worktrees:-[]}" '
|
|
def tilde: if startswith($home) then "~" + .[($home | length):] else . end;
|
|
def ago: if . < 60 then "\(.)s" elif . < 3600 then "\(. / 60 | floor)m"
|
|
else "\(. / 3600 | floor)h\(. % 3600 / 60 | floor)m" end;
|
|
def run($mode; $argv): {shell: ([$self, $mode, $root] + $argv | map(@sh) | join(" "))};
|
|
# An agent that registered but reported nothing yet has status "-".
|
|
{working: {icon: "\uf110", color: "#5ea1ff", rank: 2, label: "working"},
|
|
waiting: {icon: "\uf075", color: "#f1ff5e", rank: 0, label: "waiting"},
|
|
done: {icon: "\uf00c", color: "#5eff6c", rank: 1, label: "done"}} as $look
|
|
| {icon: "\uf544", color: "#7b8496", rank: 3, label: "idle"} as $idle
|
|
| ($worktrees | map(select(.is_main | not))) as $trees
|
|
| (if $root == "" then "" else $root | split("/") | last end) as $repo
|
|
| [
|
|
((.agents // []) | map(. + {look: ($look[.status // ""] // $idle)})
|
|
| sort_by([.look.rank, (.elapsed_secs // 0)])[]
|
|
| {category: "Agents", icon: .look.icon, iconColor: .look.color,
|
|
title: (if .worktree == .project then .project else "\(.project)/\(.worktree)" end),
|
|
description: ([.look.label + (if .elapsed_secs == null then "" else " \(.elapsed_secs | ago)" end),
|
|
(if (.title // "") == "" then .session else .title end)] | join(" · ")),
|
|
action: {tmux: "switch-client -t \(.pane_id | @sh)"}}),
|
|
($trees[]
|
|
| {category: "Worktrees: \($repo)", icon: "\uf126",
|
|
title: .handle,
|
|
description: ([.branch, (if .is_open then "open" else "closed" end)]
|
|
+ (if .has_uncommitted_changes then ["dirty"] else [] end)
|
|
+ [.path | tilde] | join(" · ")),
|
|
action: run("--do"; ["workmux", "open", .handle])}
|
|
+ (if .is_open then {iconColor: "#5eff6c"} else {} end)),
|
|
(if $root == "" then empty else
|
|
{category: "New", icon: "\uf067", title: "New worktree from a prompt",
|
|
description: "type the task (gum write); omp names the branch · \($repo)",
|
|
aliases: ["add", "prompt"], action: run("--popup"; [$self, "--prompt"])}
|
|
end),
|
|
{category: "Workmux", icon: "\uf0e4", title: "Dashboard", description: "agents and worktrees TUI in a popup",
|
|
aliases: ["dash"], action: {tmux: "display-popup -E -w 90% -h 90% workmux dashboard"}},
|
|
{category: "Workmux", icon: "\uf0db", title: "Sidebar", description: "toggle the agent status sidebar",
|
|
shortcut: "Ctrl+Shift+S", aliases: ["side"], action: {tmux: "run-shell -b \("workmux sidebar" | @sh)"}},
|
|
{category: "Workmux", icon: "\uf0e2", title: "Last done", description: "jump to the agent that last finished or is waiting",
|
|
aliases: ["ld"], action: {tmux: "run-shell -b \("workmux last-done" | @sh)"}},
|
|
($trees[] | select(.is_open)
|
|
| {category: "Close", icon: "\uf00d", title: "Close \(.handle)", description: "close the session, keep the worktree and branch",
|
|
action: run("--do"; ["workmux", "close", .handle])}),
|
|
($trees[]
|
|
| {category: "Merge", icon: "\uf419", title: "Merge \(.handle)", description: "merge \(.branch) into the main branch, then remove it",
|
|
action: run("--popup"; ["workmux", "merge", .handle])}),
|
|
($trees[]
|
|
| {category: "Remove", icon: "\uf1f8", title: "Remove \(.handle)", description: "remove the worktree, session and branch without merging",
|
|
action: run("--popup"; ["workmux", "remove", .handle])})
|
|
]'
|