#!/bin/bash # devdesk-notify-matrix SESSION SINCE # devdesk-notify-matrix --usage PCT RESETS_AT # # Started (detached) by devdesk-state-hook when a session starts waiting on you. # After DEVDESK_MATRIX_DELAY seconds, if that same wait is still on, no DevDesk # tab is in view, and this session hasn't posted in DEVDESK_MATRIX_QUIET seconds, # post what Claude is asking to the hookshot webhook in DEVDESK_MATRIX_WEBHOOK_FILE # (devdesk#28, #50). set -euo pipefail # shellcheck source=libexec/devdesk-lib.sh . "${DEVDESK_LIBEXEC:-/usr/lib/devdesk}/devdesk-lib.sh" # Post BODY (JSON with a .text) to the hookshot webhook. The URL is a secret # (anyone holding it can post to the room): it goes to curl on stdin, never on # the command line. post() { local webhook # shellcheck disable=SC1003 # tr deletes whitespace, quotes and backslashes from the URL webhook=$(tr -d '[:space:]"\\' <"$DEVDESK_MATRIX_WEBHOOK_FILE") printf 'url = "%s"\n' "$webhook" | curl -fsS --max-time 10 --retry 2 -K - -H 'Content-Type: application/json' --data-binary "$1" >/dev/null } # devdesk-notify-matrix --usage PCT RESETS_AT (devdesk#45): started by the status # line when the 5-hour plan window passes DEVDESK_USAGE_WARN percent. Once per # window, and only when no client is attached to any session (you're away; a # browser tab shows its own warning). if [ "${1:-}" = --usage ]; then pct=${2:-} resets=${3:-} [[ $pct =~ ^[0-9]+$ && $resets =~ ^[0-9]+$ ]] || exit 0 marker="$DEVDESK_STATE_DIR/usage-warned" exec 9>>"$marker.lock" flock -n 9 || exit 0 [ "$(cat "$marker" 2>/dev/null)" != "$resets" ] || exit 0 printf '%s\n' "$resets" >"$marker" [ -r "$DEVDESK_MATRIX_WEBHOOK_FILE" ] || exit 0 [ -z "$(dd_tmux list-clients -F x 2>/dev/null)" ] || exit 0 at=$(date -d "@$resets" '+%H:%M %Z') post "$(jq -cMn --arg p "$pct" --arg at "$at" --arg h "$(hostname)" \ '{kind: "usage", host: $h, text: "Claude plan usage is at \($p)% of the 5-hour window; it resets at \($at)."}')" exit 0 fi session=${1:-} since=${2:-} dd_valid_name "$session" || exit 0 [[ $since =~ ^[0-9]+$ ]] || exit 0 [ -r "$DEVDESK_MATRIX_WEBHOOK_FILE" ] || exit 0 sleep "$DEVDESK_MATRIX_DELAY" state_file="$DEVDESK_STATUS_DIR/$session.json" # Still the same wait? (Answering it moves the state on; a new wait has its own notifier.) [ "$(jq -rM '"\(.state) \(.since)"' "$state_file" 2>/dev/null)" = "waiting $since" ] || exit 0 # You're at DevDesk if any tab has shown a session in the last ~2 minutes # (presence markers, devdesk#26): the browser tells you itself (devdesk#50). [ -z "$(find "$DEVDESK_PRESENCE_DIR" -type f 2>/dev/null | head -n 1)" ] || exit 0 # The idle reminder isn't a request (the state hook drops it; old states may have it). message=$(jq -rM '.message // ""' "$state_file" 2>/dev/null) request=$(jq -cM '.request // null' "$state_file" 2>/dev/null) || request=null if [ "$request" = null ] && { [ -z "$message" ] || [[ $message == *"waiting for your input"* ]]; }; then exit 0; fi # At most one message per session per DEVDESK_MATRIX_QUIET seconds. sent="$DEVDESK_STATE_DIR/matrix-sent/$session" mkdir -p "${sent%/*}" if [ -n "$(find "$sent" -newermt "-$DEVDESK_MATRIX_QUIET seconds" 2>/dev/null)" ]; then exit 0; fi touch "$sent" url="" # existing=1: if the session was ended by the time you tap, say so instead of # starting a new Claude there (devdesk#38). [ -z "$DEVDESK_PUBLIC_URL" ] || url="${DEVDESK_PUBLIC_URL%/}/?arg=$session&existing=1" # What exactly it wants: "alpha: Claude wants to run Bash (Remove x)" and the command. body=$(jq -cMn --arg s "$session" --arg m "$message" --argjson r "$request" --arg u "$url" --arg h "$(hostname)" ' (if $r then "\($s): Claude wants to run \($r.tool)\(if $r.description then " (\($r.description))" else "" end)" else "\($s): \($m)" end) as $head | ($r.detail // null | if . then .[0:300] else null end) as $d | {session: $s, message: $m, url: (if $u == "" then null else $u end), host: $h, text: ($head + (if $d then "\n" + $d else "" end) + (if $u == "" then "" else "\nAnswer: " + $u end))}') post "$body"