master
John Lauer tools: wiki-issue-monitor.sh — the wiki-issue-loop live Monitor (John: 'do a monitor every 5 mins today'). Seeds on the… 98e767f 2mo ago
#!/usr/bin/env bash
# wiki-issue-monitor.sh — poll the wiki notification feed and emit ONE LINE PER NEW ITEM.
#
# John, 2026-08-21: "do a monitor every 5 mins today to see if wiki issues are given to you."
# This is the `wiki-issue-loop` skill's option 2 (the live Monitor) — the one that makes same-hour
# issue ping-pong work, instead of waiting up to 30 min for the UserPromptSubmit nudge hook.
#
# TWO THINGS THIS GETS RIGHT, both of which are easy to get wrong:
#   1. It SEEDS on the current unread set. The feed had 387 unread when this was written; emitting
#      those would be a 387-notification firehose and the monitor would be auto-stopped for volume.
#      Only items that appear AFTER the seed are events.
#   2. A failed poll is never fatal and never an event. curl/parse failures leave the seen-set
#      untouched and the loop continues, so one blip cannot kill the watch or fake an event.
#
# Output is deliberately narrow: one line per new notification, marked [MINE] when it lands on a repo
# this thread maintains, because those are the ones that change what I do next.
set -u
KEY_FILE="/var/run/adom/api-key"
INTERVAL="${WIM_INTERVAL:-300}"
MINE_RE="${WIM_MINE:-pup-bridge}"
SEEN="${TMPDIR:-/tmp}/.wiki-issue-monitor-seen"

[ -r "$KEY_FILE" ] || { echo "wiki-monitor: no api key at $KEY_FILE — cannot watch"; exit 1; }

fetch() {
  curl -s --max-time 20 -H "Authorization: Bearer $(cat "$KEY_FILE")" \
    "https://wiki.adom.inc/api/me/notifications?unread=1&limit=100" 2>/dev/null || true
}

# ── seed: everything already unread is history, not an event ────────────────────────────────────
fetch | python3 -c '
import sys,json
try: d=json.load(sys.stdin)
except Exception: sys.exit(0)
for n in (d.get("notifications") or []):
    print(n.get("id"))
' > "$SEEN" 2>/dev/null || true
echo "wiki-monitor: armed — $(wc -l < "$SEEN" 2>/dev/null || echo 0) existing unread seeded as history; polling every ${INTERVAL}s for NEW items only"

while true; do
  sleep "$INTERVAL"
  fetch | python3 -c '
import sys,json,os,re
seen_path=os.environ["SEEN"]; mine=re.compile(os.environ.get("MINE_RE","pup-bridge"))
try: d=json.load(sys.stdin)
except Exception: sys.exit(0)                      # bad/empty response: not an event, not a failure
items=d.get("notifications")
if items is None: sys.exit(0)
try: seen=set(x.strip() for x in open(seen_path) if x.strip())
except Exception: seen=set()
fresh=[]
for n in items:
    i=str(n.get("id") or "")
    if not i or i in seen: continue
    fresh.append(n); seen.add(i)
for n in fresh:
    # Field names verified against a live payload (2026-08-21): ref / actor / type / title /
    # reason / issue_status. Guessing these produced "? — update by someone" on the first run.
    ref=str(n.get("ref") or "?")
    who=n.get("actor") or "someone"
    ev=(n.get("type") or "update").replace("_"," ")
    st=n.get("issue_status") or ""
    why=n.get("reason") or ""
    title=re.sub(r"\s+"," ",str(n.get("title") or ""))[:80]
    tag="[MINE] " if mine.search(ref) else ""
    extra=" ".join(x for x in (f"({st})" if st else "", f"[{why}]" if why else "") if x)
    print(f"{tag}wiki: {ref} — {ev} by {who} {extra}: \"{title}\"", flush=True)
if fresh:
    try:
        with open(seen_path,"w") as f: f.write("\n".join(sorted(seen))+"\n")
    except Exception: pass
' SEEN="$SEEN" MINE_RE="$MINE_RE" 2>/dev/null || true
done