sync: auto-sync from GURU-5070 at 2026-06-15 09:41:53

Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 09:41:53
This commit is contained in:
2026-06-15 09:42:15 -07:00
parent 153be4abec
commit dc5c09b40b
26 changed files with 537 additions and 13 deletions

View File

@@ -0,0 +1,106 @@
#!/usr/bin/env bash
# discord-dm.sh — send a Discord message to an org member (DM) or a team channel,
# using the ClaudeTools bot. Built for copy-paste-friendly delivery of wrapped
# command lines (consent links, long one-liners) straight to a person's DMs.
#
# Usage:
# discord-dm.sh <recipient> "message text"
# echo "message text" | discord-dm.sh <recipient>
# discord-dm.sh list # show known users + channels
#
# <recipient> is one of:
# - a user name: mike | howard | rob | winter -> opens a DM
# - a channel: #bot-alerts | #dev-alerts | bot | dev -> posts to the channel
# - a raw snowflake: 17-19 digit ID (treated as a USER DM; prefix chan:<id> for a channel)
#
# Token resolution (mirrors post-bot-alert.sh, first hit wins):
# 1. SOPS vault: projects/discord-bot/bot-token.sops.yaml field credentials.bot_token
# 2. projects/discord-bot/.env key DISCORD_TOKEN
#
# Exit codes: 0 success; 1 usage/arg error; 2 token missing; 3 Discord API error.
# Unlike post-bot-alert.sh this does NOT soft-fail — a DM is usually load-bearing
# (you asked for it on purpose), so a failure is surfaced with a non-zero exit.
set -u
ROOT="${CLAUDETOOLS_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
API="https://discord.com/api/v10"
UA="ClaudeToolsBot (claudetools, 1.0)"
# --- prepopulated org directory (from .claude/users.json + post-bot-alert.sh) ---
declare -A USERS=(
[mike]="264814939619721216"
[howard]="624667664501178379"
[rob]="261978810713505792"
[winter]="624666486362996755"
)
declare -A CHANNELS=(
[bot-alerts]="624710699771232265" # whole team (Syncro + general)
[dev-alerts]="1509998508198068484" # private RMM/Dev (Howard + Mike)
)
print_dir() {
echo "Users (DM):"
for u in "${!USERS[@]}"; do printf " %-8s %s\n" "$u" "${USERS[$u]}"; done
echo "Channels:"
for c in "${!CHANNELS[@]}"; do printf " #%-12s %s\n" "$c" "${CHANNELS[$c]}"; done
}
RECIPIENT="${1:-}"
if [ -z "$RECIPIENT" ] || [ "$RECIPIENT" = "-h" ] || [ "$RECIPIENT" = "--help" ]; then
sed -n '2,30p' "$0"; exit 1
fi
if [ "$RECIPIENT" = "list" ]; then print_dir; exit 0; fi
shift
# --- message (remaining args, else stdin) ---
if [ "$#" -gt 0 ]; then MSG="$*"; elif [ ! -t 0 ]; then MSG="$(cat)"; else MSG=""; fi
if [ -z "$MSG" ]; then echo "[ERROR] empty message — nothing sent" >&2; exit 1; fi
# --- resolve recipient -> mode (dm|channel) + target id ---
MODE=""; TARGET=""; LABEL=""
key="$(printf '%s' "$RECIPIENT" | tr '[:upper:]' '[:lower:]')"
case "$key" in
bot|bot-alerts|"#bot-alerts") MODE=channel; TARGET="${CHANNELS[bot-alerts]}"; LABEL="#bot-alerts" ;;
dev|dev-alerts|"#dev-alerts") MODE=channel; TARGET="${CHANNELS[dev-alerts]}"; LABEL="#dev-alerts" ;;
chan:*) MODE=channel; TARGET="${key#chan:}"; LABEL="channel ${TARGET}" ;;
\#*) c="${key#\#}"; if [ -n "${CHANNELS[$c]:-}" ]; then MODE=channel; TARGET="${CHANNELS[$c]}"; LABEL="#$c"; else echo "[ERROR] unknown channel: $RECIPIENT (try: discord-dm.sh list)" >&2; exit 1; fi ;;
*)
if [ -n "${USERS[$key]:-}" ]; then MODE=dm; TARGET="${USERS[$key]}"; LABEL="$key (DM)"
elif printf '%s' "$key" | grep -qE '^[0-9]{17,19}$'; then MODE=dm; TARGET="$key"; LABEL="user ${key} (DM)"
else echo "[ERROR] unknown recipient: $RECIPIENT (try: discord-dm.sh list)" >&2; exit 1; fi ;;
esac
# --- bot token: vault first, then .env ---
TOKEN="$(bash "$ROOT/.claude/scripts/vault.sh" get-field \
projects/discord-bot/bot-token.sops.yaml credentials.bot_token 2>/dev/null)"
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
ENV_FILE="$ROOT/projects/discord-bot/.env"
[ -f "$ENV_FILE" ] && TOKEN="$(grep -iE '^[[:space:]]*DISCORD_TOKEN[[:space:]]*=' "$ENV_FILE" | head -1 | sed -E 's/^[^=]*=[[:space:]]*//; s/^["'"'"']//; s/["'"'"'][[:space:]]*$//')"
fi
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then echo "[ERROR] no bot token (vault + .env both empty)" >&2; exit 2; fi
auth=(-H "Authorization: Bot ${TOKEN}" -H "Content-Type: application/json" -H "User-Agent: ${UA}")
# --- for a DM, open (or fetch) the user's DM channel ---
if [ "$MODE" = "dm" ]; then
DM="$(printf '%s' "$(jq -nc --arg r "$TARGET" '{recipient_id:$r}')" | \
curl -s -m 15 "${auth[@]}" -X POST "$API/users/@me/channels" --data-binary @-)"
CHID="$(printf '%s' "$DM" | jq -r '.id // empty' 2>/dev/null)"
if [ -z "$CHID" ]; then echo "[ERROR] could not open DM channel for $LABEL: $DM" >&2; exit 3; fi
TARGET="$CHID"
fi
# --- post the message (printf | --data-binary @- — direct -d mangles multiline JSON) ---
RESP="$(printf '%s' "$(jq -nc --arg c "$MSG" '{content:$c}')" | \
curl -s -m 15 -w $'\n%{http_code}' "${auth[@]}" \
-X POST "$API/channels/${TARGET}/messages" --data-binary @-)"
HTTP="$(printf '%s' "$RESP" | tail -n1)"
BODY="$(printf '%s' "$RESP" | sed '$d')"
if [ "$HTTP" = "200" ]; then
echo "[OK] discord-dm: sent to ${LABEL} (message_id=$(printf '%s' "$BODY" | jq -r '.id // empty'))"
exit 0
fi
echo "[ERROR] discord-dm: Discord returned ${HTTP:-no-response}${BODY}" >&2
exit 3