ScuttleBot

scuttlebot / skills / gemini-relay / hooks / scuttlebot-after-agent.sh
Source Blame History 122 lines
016a29f… lmata 1 #!/bin/bash
016a29f… lmata 2 # AfterAgent hook for Gemini agents. Posts final assistant replies to scuttlebot IRC.
016a29f… lmata 3
016a29f… lmata 4 SCUTTLEBOT_CONFIG_FILE="${SCUTTLEBOT_CONFIG_FILE:-$HOME/.config/scuttlebot-relay.env}"
016a29f… lmata 5 if [ -f "$SCUTTLEBOT_CONFIG_FILE" ]; then
016a29f… lmata 6 set -a
016a29f… lmata 7 . "$SCUTTLEBOT_CONFIG_FILE"
016a29f… lmata 8 set +a
016a29f… lmata 9 fi
1d3caa2… lmata 10 if [ -n "${SCUTTLEBOT_CHANNEL_STATE_FILE:-}" ] && [ -f "$SCUTTLEBOT_CHANNEL_STATE_FILE" ]; then
1d3caa2… lmata 11 set -a
1d3caa2… lmata 12 . "$SCUTTLEBOT_CHANNEL_STATE_FILE"
1d3caa2… lmata 13 set +a
1d3caa2… lmata 14 fi
016a29f… lmata 15
016a29f… lmata 16 SCUTTLEBOT_URL="${SCUTTLEBOT_URL:-http://localhost:8080}"
016a29f… lmata 17 SCUTTLEBOT_TOKEN="${SCUTTLEBOT_TOKEN}"
016a29f… lmata 18 SCUTTLEBOT_CHANNEL="${SCUTTLEBOT_CHANNEL:-general}"
016a29f… lmata 19 SCUTTLEBOT_HOOKS_ENABLED="${SCUTTLEBOT_HOOKS_ENABLED:-1}"
016a29f… lmata 20 SCUTTLEBOT_AFTER_AGENT_MAX_POSTS="${SCUTTLEBOT_AFTER_AGENT_MAX_POSTS:-6}"
016a29f… lmata 21 SCUTTLEBOT_AFTER_AGENT_CHUNK_WIDTH="${SCUTTLEBOT_AFTER_AGENT_CHUNK_WIDTH:-360}"
1d3caa2… lmata 22
1d3caa2… lmata 23 normalize_channel() {
1d3caa2… lmata 24 local channel="$1"
1d3caa2… lmata 25 channel="${channel//[$' \t\r\n']/}"
1d3caa2… lmata 26 channel="${channel#\#}"
1d3caa2… lmata 27 printf '%s' "$channel"
1d3caa2… lmata 28 }
1d3caa2… lmata 29
1d3caa2… lmata 30 relay_channels() {
1d3caa2… lmata 31 local raw="${SCUTTLEBOT_CHANNELS:-$SCUTTLEBOT_CHANNEL}"
1d3caa2… lmata 32 local IFS=','
1d3caa2… lmata 33 local item channel seen=""
1d3caa2… lmata 34 read -r -a items <<< "$raw"
1d3caa2… lmata 35 for item in "${items[@]}"; do
1d3caa2… lmata 36 channel=$(normalize_channel "$item")
1d3caa2… lmata 37 [ -n "$channel" ] || continue
1d3caa2… lmata 38 case ",$seen," in
1d3caa2… lmata 39 *,"$channel",*) ;;
1d3caa2… lmata 40 *)
1d3caa2… lmata 41 seen="${seen:+$seen,}$channel"
1d3caa2… lmata 42 printf '%s\n' "$channel"
1d3caa2… lmata 43 ;;
1d3caa2… lmata 44 esac
1d3caa2… lmata 45 done
1d3caa2… lmata 46 }
016a29f… lmata 47
016a29f… lmata 48 sanitize() {
016a29f… lmata 49 local input="$1"
016a29f… lmata 50 if [ -z "$input" ]; then
016a29f… lmata 51 input=$(cat)
016a29f… lmata 52 fi
016a29f… lmata 53 printf '%s' "$input" | tr -cs '[:alnum:]_-' '-'
016a29f… lmata 54 }
016a29f… lmata 55
016a29f… lmata 56 post_line() {
016a29f… lmata 57 local text="$1"
1d3caa2… lmata 58 local payload
016a29f… lmata 59 [ -z "$text" ] && return 0
1d3caa2… lmata 60 payload="{\"text\": $(printf '%s' "$text" | jq -Rs .), \"nick\": \"$SCUTTLEBOT_NICK\"}"
1d3caa2… lmata 61 for channel in $(relay_channels); do
1d3caa2… lmata 62 curl -sf -X POST "$SCUTTLEBOT_URL/v1/channels/$channel/messages" \
1d3caa2… lmata 63 --connect-timeout 1 \
1d3caa2… lmata 64 --max-time 2 \
1d3caa2… lmata 65 -H "Authorization: Bearer $SCUTTLEBOT_TOKEN" \
1d3caa2… lmata 66 -H "Content-Type: application/json" \
1d3caa2… lmata 67 -d "$payload" \
1d3caa2… lmata 68 > /dev/null || true
1d3caa2… lmata 69 done
016a29f… lmata 70 }
016a29f… lmata 71
016a29f… lmata 72 normalize_response() {
016a29f… lmata 73 printf '%s' "$1" \
016a29f… lmata 74 | tr '\r\n\t' ' ' \
016a29f… lmata 75 | tr -s '[:space:]' ' ' \
016a29f… lmata 76 | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
016a29f… lmata 77 }
016a29f… lmata 78
016a29f… lmata 79 input=$(cat)
016a29f… lmata 80
016a29f… lmata 81 cwd=$(echo "$input" | jq -r '.cwd // empty')
016a29f… lmata 82 if [ -z "$cwd" ]; then
016a29f… lmata 83 cwd=$(pwd)
016a29f… lmata 84 fi
016a29f… lmata 85 base_name=$(sanitize "$(basename "$cwd")")
016a29f… lmata 86 session_raw="${SCUTTLEBOT_SESSION_ID:-${GEMINI_SESSION_ID:-$PPID}}"
016a29f… lmata 87 if [ -z "$session_raw" ] || [ "$session_raw" = "0" ]; then
016a29f… lmata 88 session_raw=$(date +%s)
016a29f… lmata 89 fi
016a29f… lmata 90 session_suffix=$(printf '%s' "$session_raw" | sanitize | cut -c 1-8)
016a29f… lmata 91 default_nick="gemini-${base_name}-${session_suffix}"
016a29f… lmata 92 SCUTTLEBOT_NICK="${SCUTTLEBOT_NICK:-$default_nick}"
016a29f… lmata 93
016a29f… lmata 94 [ "$SCUTTLEBOT_HOOKS_ENABLED" = "0" ] && { echo '{}'; exit 0; }
016a29f… lmata 95 [ "$SCUTTLEBOT_HOOKS_ENABLED" = "false" ] && { echo '{}'; exit 0; }
016a29f… lmata 96 [ -z "$SCUTTLEBOT_TOKEN" ] && { echo '{}'; exit 0; }
016a29f… lmata 97
016a29f… lmata 98 response=$(echo "$input" | jq -r '.prompt_response // empty')
016a29f… lmata 99 [ -z "$response" ] && { echo '{}'; exit 0; }
016a29f… lmata 100
016a29f… lmata 101 response=$(normalize_response "$response")
016a29f… lmata 102 [ -z "$response" ] && { echo '{}'; exit 0; }
016a29f… lmata 103
016a29f… lmata 104 posted=0
016a29f… lmata 105 truncated=0
016a29f… lmata 106 while IFS= read -r chunk || [ -n "$chunk" ]; do
016a29f… lmata 107 chunk=$(printf '%s' "$chunk" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
016a29f… lmata 108 [ -z "$chunk" ] && continue
016a29f… lmata 109 if [ "$posted" -ge "$SCUTTLEBOT_AFTER_AGENT_MAX_POSTS" ]; then
016a29f… lmata 110 truncated=1
016a29f… lmata 111 break
016a29f… lmata 112 fi
016a29f… lmata 113 post_line "$chunk"
016a29f… lmata 114 posted=$((posted + 1))
016a29f… lmata 115 done < <(printf '%s\n' "$response" | fold -s -w "$SCUTTLEBOT_AFTER_AGENT_CHUNK_WIDTH")
016a29f… lmata 116
016a29f… lmata 117 if [ "$truncated" -eq 1 ]; then
016a29f… lmata 118 post_line "[reply truncated]"
016a29f… lmata 119 fi
016a29f… lmata 120
016a29f… lmata 121 echo '{}'
016a29f… lmata 122 exit 0

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button