diff --git a/scout.py b/scout.py
new file mode 100644
index 0000000..02ce031
--- /dev/null
+++ b/scout.py
@@ -0,0 +1,150 @@
+#!/usr/bin/env python3
+import argparse
+import datetime as dt
+import html
+import json
+import math
+import pathlib
+import urllib.request
+import urllib.error
+
+ROOT = pathlib.Path(__file__).resolve().parent
+WATCHED = [
+ "openai/codex",
+ "anthropics/claude-code",
+ "vercel-labs/agent-browser",
+ "browser-use/browser-use",
+ "block/goose",
+ "modelcontextprotocol/servers",
+]
+KEYWORDS = {
+ "browser": 9, "upload": 12, "file": 3, "preview": 10, "localhost": 8,
+ "session": 7, "context": 9, "compact": 10, "goal": 10, "plan": 7,
+ "mcp": 9, "sandbox": 10, "permission": 9, "approval": 8, "guardrail": 10,
+ "model": 5, "effort": 8, "preset": 10, "workflow": 6, "task": 5,
+ "project": 5, "sidebar": 5, "hook": 7, "export": 9, "integration": 5,
+ "extension": 7, "plugin": 8, "api": 5, "cli": 4, "agent": 5,
+}
+
+def api(url):
+ req = urllib.request.Request(url, headers={
+ "User-Agent": "notplanned-scout/0.1",
+ "Accept": "application/vnd.github+json",
+ "X-GitHub-Api-Version": "2022-11-28",
+ })
+ with urllib.request.urlopen(req, timeout=15) as r:
+ return json.load(r)
+
+def score_issue(issue, now):
+ text = (issue.get("title","") + " " + (issue.get("body") or "")).lower()
+ closed = issue.get("closed_at") or issue.get("updated_at")
+ try:
+ when = dt.datetime.fromisoformat(closed.replace("Z","+00:00"))
+ age = max(0, (now - when).days)
+ except Exception:
+ age = 365
+ recency = max(0, 90 - age)
+ comments = min(24, int(issue.get("comments") or 0) * 2)
+ reactions = issue.get("reactions") or {}
+ reaction_score = min(24, int(reactions.get("total_count") or 0) * 3)
+ keyword_score = sum(weight for word, weight in KEYWORDS.items() if word in text)
+ return recency + comments + reaction_score + min(48, keyword_score)
+
+def suggested_move(title):
+ t = title.lower()
+ if "upload" in t or "file input" in t:
+ return "Ship a deterministic upload bridge outside upstream."
+ if "preview" in t or "localhost" in t:
+ return "Ship a preview/port sidecar and test it on real agent-built apps."
+ if "context" in t or "compact" in t:
+ return "Build a transcript/context sidecar; measure saved tokens and resumability."
+ if "goal" in t or "session" in t:
+ return "Wrap session state externally; prove persistence across runs."
+ if "model" in t or "effort" in t or "preset" in t:
+ return "Compose the existing knobs into one named preset action."
+ if "permission" in t or "sandbox" in t or "approval" in t or "guardrail" in t:
+ return "Put a narrow authority policy in front of the existing agent."
+ if "browser" in t or "domain" in t or "form" in t:
+ return "Wrap browser actions with scoped policy + receipts."
+ if "project" in t or "task" in t or "workflow" in t:
+ return "Ship an external supervisor instead of waiting for upstream PM."
+ return "Find the smallest external wrapper that satisfies the request; test one user."
+
+def esc(s):
+ return html.escape(str(s), quote=True)
+
+def curated_card(o):
+ return f"""
+ {esc(o['thesis'])}{esc(o['name'])}
+ {esc(o['title'])}
+
+