app
Pup - the AI's own browser
Public Made by Adomby adom
pup gives your AI a real browser on your desktop that it drives completely, in the background, signed in as you: windows that never jump in front of your work, one shared profile that learns your logins, and every window labeled on the taskbar with its AI thread's name.
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
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
#!/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