ScuttleBot

feat: codex Stop hook for mirroring agent responses to IRC Codex CLI's Stop hook fires after each conversation turn and includes last_assistant_message. The new scuttlebot-stop.sh posts the response to all relay channels, matching the gemini AfterAgent behavior. Also updated hooks.json to register the Stop hook alongside existing pre-tool-use and post-tool-use hooks.

lmata 2026-04-04 03:56 trunk
Commit 606670ed8028ba37b08730343b86bbe94ecdfa6a6881bc5c018fc2adb8170faf
--- a/skills/openai-relay/hooks/scuttlebot-stop.sh
+++ b/skills/openai-relay/hooks/scuttlebot-stop.sh
@@ -0,0 +1,81 @@
1
+#!/bin/bash
2
+# Stop hook for Codex agents. Posts final assistant reply to scuttlebot IRC.
3
+
4
+SCUTTLEBOT_CONFIG_FILE="${SCUTTLEBOT_CONFIG_FILE:-$HOME/.config/scuttlebot-relay.env}"
5
+if [ -f "$SCUTTLEBOT_CONFIG_FILE" ]; then
6
+ set -a
7
+ . "$SCUTTLEBOT_CONFIG_FILE"
8
+ set +a
9
+fi
10
+if [ -n "${SCUTTLEBOT_CHANNEL_STATE_FILE:-}" ] && [ -f "$SCUTTLEBOT_CHANNEL_STATE_FILE" ]; then
11
+ set -a
12
+ . "$SCUTTLEBOT_CHANNEL_STATE_FILE"
13
+ set +a
14
+fi
15
+
16
+SCUTTLEBOT_URL="${SCUTTLEBOT_URL:-http://localhost:8080}"
17
+SCUTTLEBOT_TOKEN="${SCUTTLEBOT_TOKEN}"
18
+SCUTTLEBOT_CHANNEL="${SCUTTLEBOT_CHANNEL:-general}"
19
+SCUTTLEBOT_HOOKS_ENABLED="${SCUTTLEBOT_HOOKS_ENABLED:-1}"
20
+
21
+normalize_channel() {
22
+ local channel="$1"
23
+ channel="${channel//[$' \t\r\n']/}"
24
+ channel="${channel#\#}"
25
+ printf '%s' "$channel"
26
+}
27
+
28
+relay_channels() {
29
+ local raw="${SCUTTLEBOT_CHANNELS:-$SCUTTLEBOT_CHANNEL}"
30
+ local IFS=','
31
+ local item channel seen=""
32
+ read -r -a items <<< "$raw"
33
+ for item in "${items[@]}"; do
34
+ channel=$(normalize_channel "$item")
35
+ [ -n "$channel" ] || continue
36
+ case ",$seen," in
37
+ *,"$channel",*) ;;
38
+ *)
39
+ seen="${seen:+$seen,}$channel"
40
+ printf '%s\n' "$channel"
41
+ ;;
42
+ esac
43
+ done
44
+}
45
+
46
+sanitize() {
47
+ printf '%s' "$1" | tr -cs '[:alnum:]_-' '-'
48
+}
49
+
50
+input=$(cat)
51
+
52
+cwd=$(echo "$input" | jq -r '.cwd // empty')
53
+[ -z "$cwd" ] && cwd=$(pwd)
54
+base_name=$(sanitize "$(basename "$cwd")")
55
+session_suffix=$(echo "$input" | jq -r '.session_id // empty' | head -c 8)
56
+[ -z "$session_suffix" ] && session_suffix=$PPID
57
+default_nick="codex-${base_name}-$(sanitize "$session_suffix")"
58
+SCUTTLEBOT_NICK="${SCUTTLEBOT_NICK:-$default_nick}"
59
+
60
+[ "$SCUTTLEBOT_HOOKS_ENABLED" = "0" ] && exit 0
61
+[ "$SCUTTLEBOT_HOOKS_ENABLED" = "false" ] && exit 0
62
+[ -z "$SCUTTLEBOT_TOKEN" ] && exit 0
63
+
64
+response=$(echo "$input" | jq -r '.last_assistant_message // empty')
65
+[ -z "$response" ] && exit 0
66
+
67
+# Truncate long responses.
68
+response=$(printf '%s' "$response" | head -c 360)
69
+
70
+payload="{\"text\": $(printf '%s' "$response" | jq -Rs .), \"nick\": \"$SCUTTLEBOT_NICK\"}"
71
+for channel in $(relay_channels); do
72
+ curl -sf -X POST "$SCUTTLEBOT_URL/v1/channels/$channel/messages" \
73
+ --connect-timeout 1 \
74
+ --max-time 2 \
75
+ -H "Authorization: Bearer $SCUTTLEBOT_TOKEN" \
76
+ -H "Content-Type: application/json" \
77
+ -d "$payload" \
78
+ > /dev/null || true
79
+done
80
+
81
+exit 0
--- a/skills/openai-relay/hooks/scuttlebot-stop.sh
+++ b/skills/openai-relay/hooks/scuttlebot-stop.sh
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/skills/openai-relay/hooks/scuttlebot-stop.sh
+++ b/skills/openai-relay/hooks/scuttlebot-stop.sh
@@ -0,0 +1,81 @@
1 #!/bin/bash
2 # Stop hook for Codex agents. Posts final assistant reply to scuttlebot IRC.
3
4 SCUTTLEBOT_CONFIG_FILE="${SCUTTLEBOT_CONFIG_FILE:-$HOME/.config/scuttlebot-relay.env}"
5 if [ -f "$SCUTTLEBOT_CONFIG_FILE" ]; then
6 set -a
7 . "$SCUTTLEBOT_CONFIG_FILE"
8 set +a
9 fi
10 if [ -n "${SCUTTLEBOT_CHANNEL_STATE_FILE:-}" ] && [ -f "$SCUTTLEBOT_CHANNEL_STATE_FILE" ]; then
11 set -a
12 . "$SCUTTLEBOT_CHANNEL_STATE_FILE"
13 set +a
14 fi
15
16 SCUTTLEBOT_URL="${SCUTTLEBOT_URL:-http://localhost:8080}"
17 SCUTTLEBOT_TOKEN="${SCUTTLEBOT_TOKEN}"
18 SCUTTLEBOT_CHANNEL="${SCUTTLEBOT_CHANNEL:-general}"
19 SCUTTLEBOT_HOOKS_ENABLED="${SCUTTLEBOT_HOOKS_ENABLED:-1}"
20
21 normalize_channel() {
22 local channel="$1"
23 channel="${channel//[$' \t\r\n']/}"
24 channel="${channel#\#}"
25 printf '%s' "$channel"
26 }
27
28 relay_channels() {
29 local raw="${SCUTTLEBOT_CHANNELS:-$SCUTTLEBOT_CHANNEL}"
30 local IFS=','
31 local item channel seen=""
32 read -r -a items <<< "$raw"
33 for item in "${items[@]}"; do
34 channel=$(normalize_channel "$item")
35 [ -n "$channel" ] || continue
36 case ",$seen," in
37 *,"$channel",*) ;;
38 *)
39 seen="${seen:+$seen,}$channel"
40 printf '%s\n' "$channel"
41 ;;
42 esac
43 done
44 }
45
46 sanitize() {
47 printf '%s' "$1" | tr -cs '[:alnum:]_-' '-'
48 }
49
50 input=$(cat)
51
52 cwd=$(echo "$input" | jq -r '.cwd // empty')
53 [ -z "$cwd" ] && cwd=$(pwd)
54 base_name=$(sanitize "$(basename "$cwd")")
55 session_suffix=$(echo "$input" | jq -r '.session_id // empty' | head -c 8)
56 [ -z "$session_suffix" ] && session_suffix=$PPID
57 default_nick="codex-${base_name}-$(sanitize "$session_suffix")"
58 SCUTTLEBOT_NICK="${SCUTTLEBOT_NICK:-$default_nick}"
59
60 [ "$SCUTTLEBOT_HOOKS_ENABLED" = "0" ] && exit 0
61 [ "$SCUTTLEBOT_HOOKS_ENABLED" = "false" ] && exit 0
62 [ -z "$SCUTTLEBOT_TOKEN" ] && exit 0
63
64 response=$(echo "$input" | jq -r '.last_assistant_message // empty')
65 [ -z "$response" ] && exit 0
66
67 # Truncate long responses.
68 response=$(printf '%s' "$response" | head -c 360)
69
70 payload="{\"text\": $(printf '%s' "$response" | jq -Rs .), \"nick\": \"$SCUTTLEBOT_NICK\"}"
71 for channel in $(relay_channels); do
72 curl -sf -X POST "$SCUTTLEBOT_URL/v1/channels/$channel/messages" \
73 --connect-timeout 1 \
74 --max-time 2 \
75 -H "Authorization: Bearer $SCUTTLEBOT_TOKEN" \
76 -H "Content-Type: application/json" \
77 -d "$payload" \
78 > /dev/null || true
79 done
80
81 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