Files
stack/tools/tmux/send-message.sh
T

60 lines
3.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# send-message.sh — dispatch text through tmux; application acceptance unknown.
#
# Usage: send-message.sh [-L socket] -t target {-m message|-f file|-b base64}
# With no message option, reads stdin. Requires bash, tmux and base64.
# -r N is compatibility-only: no automatic retries or extra Enter presses.
# -v prints transport metadata only, never a captured private transcript.
# Exit 0: tmux accepted buffer load, paste and one Enter command.
# Exit 1: target resolution failed. Exit 2: transport failed/partial/uncertain.
# Exit 3: invalid usage/input. No exit establishes application acknowledgement.
set -uo pipefail
SOCKET_NAME=""; TARGET=""; MSG=""; FILE=""; B64=""; VERBOSE=0
usage() { printf '%s\n' 'Usage: send-message.sh [-L socket] -t target [-m message|-f file|-b base64] [-r N] [-v]' 'Exit 0 = transport dispatched; application acceptance unknown. No automatic retries.'; exit "${1:-3}"; }
while getopts 'L:t:m:f:b:r:vh' o; do
case "$o" in
L) SOCKET_NAME=$OPTARG ;; t) TARGET=$OPTARG ;; m) MSG=$OPTARG ;;
f) FILE=$OPTARG ;; b) B64=$OPTARG ;;
r) [[ "$OPTARG" =~ ^[0-9]+$ ]] || usage 3 ;;
v) VERBOSE=1 ;; h) usage 0 ;; *) usage 3 ;;
esac
done
shift "$((OPTIND - 1))"
[ "$#" -eq 0 ] && [ -n "$TARGET" ] || usage 3
if [ -n "$B64" ]; then
MSG=$(printf '%s' "$B64" | base64 -d) || { echo 'ERROR: invalid base64' >&2; exit 3; }
elif [ -n "$FILE" ]; then
MSG=$(cat -- "$FILE") || { echo 'ERROR: cannot read message file' >&2; exit 3; }
elif [ -z "$MSG" ] && [ ! -t 0 ]; then
MSG=$(cat) || exit 3
fi
[ -n "$MSG" ] || { echo 'ERROR: empty message' >&2; exit 3; }
tmux_cmd=(tmux)
[ -z "$SOCKET_NAME" ] || tmux_cmd+=(-L "$SOCKET_NAME")
EFFECTIVE_TARGET=$TARGET
if [[ "$TARGET" == =* && "$TARGET" != *:* ]]; then EFFECTIVE_TARGET="${TARGET}:0.0"; fi
# Pin one pane ID for all subsequent commands rather than resolving a moving
# session/window target independently at every transport step.
PANE=$("${tmux_cmd[@]}" display-message -p -t "$EFFECTIVE_TARGET" '#{pane_id}' 2>/dev/null) || {
echo 'ERROR: tmux target resolution failed' >&2; exit 1;
}
[[ "$PANE" =~ ^%[0-9]+$ ]] || { echo 'ERROR: invalid resolved pane identity' >&2; exit 1; }
BUF="__mosaic_send_$$_$(date +%s%N)"
cleanup() { "${tmux_cmd[@]}" delete-buffer -b "$BUF" >/dev/null 2>&1 || true; }
trap cleanup EXIT
if ! printf '%s' "$MSG" | "${tmux_cmd[@]}" load-buffer -b "$BUF" -; then
echo 'ERROR: buffer load failed; transport incomplete' >&2; exit 2
fi
# Do not retry a failed paste: failure may be partial. Bracketed paste is
# requested once; changing paste mode after failure could duplicate effects.
if ! "${tmux_cmd[@]}" paste-buffer -d -p -b "$BUF" -t "$PANE"; then
echo 'ERROR: paste failed; transport uncertain; do not blindly resend' >&2; exit 2
fi
sleep 0.5
if ! "${tmux_cmd[@]}" send-keys -t "$PANE" Enter; then
echo 'ERROR: submission key failed; transport partial; do not blindly resend' >&2; exit 2
fi
[ "$VERBOSE" -eq 0 ] || printf 'transport pane=%s; paste_calls=1; submission_keys=1\n' "$PANE"
printf '%s\n' 'transport dispatched; application acceptance unknown'
exit 0