← Commit history

tools: wiki-issue-monitor.sh — the wiki-issue-loop live Monitor (John: 'do a monitor every 5 mins today'). Seeds on the current unread set so it cannot fire a 387-notification firehose, treats a failed poll as neither an event nor fatal, and marks items on repos this thread maintains.

John Lauer ·98e767f451 ·2mo ago ·parent ce241f7
1 file changed +73
tools/wiki-issue-monitor.shadded+73
@@ -0,0 +1,73 @@+#!/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