#!/bin/bash # Thread index manager for monitor-unauthorized # # Maintains a local JSON index of Discord threads so the cron-wrapper # can determine whether to create a new thread or update an existing one. # # The index is populated BY THE AGENT (not by this script) because only # the agent has access to OpenClaw session/thread listing tools. # # This script provides helper operations: # bash index-threads.sh lookup — find thread for an IP (exit 0 = found) # bash index-threads.sh status — check if index exists and is fresh # bash index-threads.sh record — add/update an entry # bash index-threads.sh needs-refresh — exit 0 if index is missing/stale # # Index location: STATE_DIR/thread-index.json # Format: # { # "indexed_at": "2026-02-16T12:00:00Z", # "channel_id": "1471181304782389381", # "threads": { # "1.2.3.4": { # "session_key": "agent:security:discord:channel:1471181304782389381:thread:🚨 1.2.3.4 — unauthorized gateway access", # "thread_name": "🚨 1.2.3.4 — unauthorized gateway access", # "first_indexed": "2026-02-10T08:00:00Z", # "last_updated": "2026-02-16T12:00:00Z", # "update_count": 3 # } # } # } set -e STATE_DIR="/home/node/.openclaw/workspace-security/memory" INDEX_FILE="$STATE_DIR/thread-index.json" CHANNEL_ID="1471181304782389381" MAX_AGE=86400 # 24 hours before considered stale mkdir -p "$STATE_DIR" # Initialize empty index if missing init_index() { if [ ! -f "$INDEX_FILE" ] || [ ! -s "$INDEX_FILE" ]; then jq -n --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg ch "$CHANNEL_ID" \ '{indexed_at: $ts, channel_id: $ch, threads: {}}' > "$INDEX_FILE" fi } # Check if index needs refresh (missing, empty, or stale) needs_refresh() { if [ ! -f "$INDEX_FILE" ] || [ ! -s "$INDEX_FILE" ]; then echo "missing" return 0 fi local age=$(( $(date +%s) - $(stat -c%Y "$INDEX_FILE" 2>/dev/null || echo 0) )) if [ "$age" -gt "$MAX_AGE" ]; then echo "stale" return 0 fi echo "fresh" return 1 } # Look up a thread by IP lookup() { local ip="$1" init_index local result result=$(jq -e --arg ip "$ip" '.threads[$ip] // empty' "$INDEX_FILE" 2>/dev/null) if [ -n "$result" ]; then echo "$result" return 0 fi return 1 } # Record a thread entry for an IP record() { local ip="$1" local session_key="$2" local thread_name="${3:-🚨 $ip — unauthorized gateway access}" local now now=$(date -u +%Y-%m-%dT%H:%M:%SZ) init_index local tmp tmp=$(mktemp) jq --arg ip "$ip" \ --arg sk "$session_key" \ --arg tn "$thread_name" \ --arg now "$now" \ ' .threads[$ip] = ( (.threads[$ip] // {first_indexed: $now, update_count: 0}) | .session_key = $sk | .thread_name = $tn | .last_updated = $now | .update_count = (.update_count + 1) ) ' "$INDEX_FILE" > "$tmp" && mv "$tmp" "$INDEX_FILE" echo '{"ok":true}' } # Print index status status() { init_index local count count=$(jq '.threads | length' "$INDEX_FILE") local indexed_at indexed_at=$(jq -r '.indexed_at' "$INDEX_FILE") local age=$(( $(date +%s) - $(stat -c%Y "$INDEX_FILE" 2>/dev/null || echo 0) )) echo "{\"entries\":$count,\"indexed_at\":\"$indexed_at\",\"age_seconds\":$age,\"stale\":$([ $age -gt $MAX_AGE ] && echo true || echo false)}" } # Dispatch case "${1:-status}" in lookup) lookup "$2" ;; record) record "$2" "$3" "$4" ;; status) status ;; needs-refresh) needs_refresh ;; *) echo "Usage: $0 {lookup|record|status|needs-refresh} [args...]" >&2; exit 1 ;; esac