feat: party knock asks an invite-only party's host for an invite

party list already shows invite-only parties; knock closes the loop.
The knocker can't run any tmux command against the server, so the
channel is write(1), the same one invite uses toward guests, except
here delivery failure is surfaced: the ping is the whole feature.
This commit is contained in:
veg 2026-07-11 12:24:31 +00:00
parent 7770289d85
commit 6799a57405
5 changed files with 163 additions and 4 deletions

65
party
View file

@ -55,6 +55,7 @@ Joining:
--passive lands in the host's session (mirrored view).
Read-only invitees always join passive.
leave Detach from the party you joined.
knock [name] Ask an invite-only party's host for an invite.
role [active|passive|switch]
Flip your clients between guest session and host session.
@ -1099,6 +1100,69 @@ cmd_leave() {
[ "$found" -gt 0 ] || { echo "party: not joined to any party." >&2; exit 1; }
}
# Send the knock ping. Factored out of cmd_knock so the write(1)
# interaction is testable: the CLI path to it needs a party hosted by
# a DIFFERENT user, and roster trust anchors on dir ownership, which a
# single-uid test run cannot fake.
knock_send() {
write "$1" 2>/dev/null <<EOF
$USER wants to join '$2'.
Run: party invite $USER --party $2
EOF
}
cmd_knock() {
name=
while [ $# -gt 0 ]; do
case "$1" in
-h|--help)
cat <<'EOF'
Usage: party knock [name]
Ask a party's host for an invite. For parties `party list` shows as
invite-only: you share the group, you're just not on the allowlist.
The host gets a write(1) ping naming you and the invite command.
EOF
exit 0 ;;
-*) echo "party knock: unknown flag '$1'" >&2; exit 2 ;;
*)
if [ -z "$name" ]; then name="$1"; shift
else echo "party knock: unexpected arg '$1'" >&2; exit 2; fi
;;
esac
done
if [ -n "$name" ]; then
find_party_by_name "$name" && rc=0 || rc=$?
case $rc in
0) ;;
1) echo "party: '$name' not found." >&2; exit 1 ;;
2) exit 1 ;; # find_party_by_name printed the ambiguity hint
esac
else
pick_live_party || exit 1
fi
if [ "$RR_HOST_USER" = "$USER" ]; then
echo "party knock: '$RR_PARTY_NAME' is your own party." >&2
exit 1
fi
if [ "$(party_conn_state "$RR_SOCKET")" = ok ]; then
echo "party knock: you are already invited. Run: party join $RR_PARTY_NAME"
exit 0
fi
# Unlike cmd_invite's courtesy ping, delivery failure is surfaced:
# the ping IS the feature. write(1) exits nonzero when the target
# has no tty or has messages disabled (mesg n).
if knock_send "$RR_HOST_USER" "$RR_PARTY_NAME"; then
echo "Knocked: $RR_HOST_USER was pinged about '$RR_PARTY_NAME'."
else
echo "party knock: could not reach $RR_HOST_USER (no tty, or messages disabled); ask them directly." >&2
exit 1
fi
}
# Resolve the party the caller hosts. If exactly one matches, sets RR_*;
# if zero or multiple, error. Argument: optional party name to disambiguate.
resolve_authoritative_party() {
@ -1485,6 +1549,7 @@ dispatch() {
close) cmd_close "$@" ;;
join) cmd_join "$@" ;;
leave) cmd_leave "$@" ;;
knock) cmd_knock "$@" ;;
invite) cmd_invite "$@" ;;
voice|rw) cmd_voice "$@" ;;
mute|ro) cmd_mute "$@" ;;