The credibility pass on Hnefatafl, ported to the template: the ops scripts count traffic through one parser, the reports digest is one program, the plaintext-token fallback and its migration are gone, and the stylesheet holds each shared rule once
From the hnefatafl repo's pass of the same day, everything that touched a kit-shared file. The visitors digest and the nightly rollup share deploy/traffic.py, installed to /usr/local/lib/<slug>; the rollup writes the finished-games count it computed behind "and False". pull-reports.sh and the reports skill both use deploy/report-digest.ts. The seat line requires its token hash and the start line its rules revision; the migration for ledgers written before hashing goes with them. The route table in server/src/index.ts lists every route; Report, ReportLine and Tally are declared once in view.ts for both sides; exports nobody imported are exports no more. In the client: .small, the × that dismisses, and the frame of the reading pages are in app.css once; the preferences panel shares the report slip's modal shape; the room store gains seatEmpty and seatUnheld, and the lobby and the join page read those instead of three spellings of their own. The room's moved and awaiting fields stay: the demo board reads them, and simultaneous rounds are the contract. The deploy README no longer describes a browser-only game; the visitors skill no longer names a /play route; the reports skill's replay call carries the room's options. The Slack channel id is passed in the environment rather than filled in as a placeholder. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwFKMuQnPAEHJ5yA1q4orh
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
6c42e55f59
commit
33ac0ec808
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
# The visitors digest: who is at the table right now and who came today,
|
||||
# the rooms opened and how far each got, with today's traffic so far.
|
||||
# Runs ON the droplet: __SLUG__-visitors.sh [YYYY-MM-DD] (default: today, UTC)
|
||||
# Every game, bot games included, is a room with a ledger, so all show in full.
|
||||
set -u
|
||||
DAY="${1:-$(date -u +%F)}"
|
||||
python3 - "$DAY" <<'PY'
|
||||
import datetime, glob, json, subprocess, sys
|
||||
sys.path.insert(0, "/usr/local/lib/__SLUG__")
|
||||
import traffic
|
||||
|
||||
day = sys.argv[1]
|
||||
now = datetime.datetime.now(datetime.timezone.utc)
|
||||
print("as of %s UTC, day %s" % (now.strftime("%H:%M"), day))
|
||||
|
||||
def sh(cmd):
|
||||
try:
|
||||
return subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=20).stdout.strip()
|
||||
except Exception:
|
||||
return "?"
|
||||
|
||||
print("live sockets: %s | rooms touched in the last hour: %s" % (
|
||||
sh("ss -tn state established '( sport = :__PORT__ )' | tail -n +2 | wc -l"),
|
||||
sh("find /var/lib/__SLUG__/rooms -name '*.jsonl' -mmin -60 2>/dev/null | wc -l")))
|
||||
|
||||
# --- traffic: Caddy's access log, people and bots apart ---
|
||||
t = traffic.scan(day)
|
||||
print("traffic: %d addresses of people, %d page requests (%d bot addresses); hall %d, rules %d, guide %d, room links %s; referrers %s; campaigns %s" % (
|
||||
len(t["addresses"]), t["human"], len(t["botAddresses"]), t["pages"]["hall"], t["pages"]["rules"], t["pages"]["guide"],
|
||||
dict(t["joins"]) or "none", dict(t["referrers"]) or "none", dict(t["campaigns"]) or "none"))
|
||||
|
||||
# --- rooms: every ledger, today's in detail ---
|
||||
first_seen = {}
|
||||
rooms = []
|
||||
for f in glob.glob("/var/lib/__SLUG__/rooms/*.jsonl"):
|
||||
try:
|
||||
lines = [json.loads(l) for l in open(f) if l.strip()]
|
||||
meta = lines[0]
|
||||
assert meta["t"] == "room"
|
||||
except Exception:
|
||||
continue
|
||||
created = datetime.datetime.fromtimestamp(meta["createdAt"] / 1000, datetime.timezone.utc)
|
||||
humans, bots_seated, started, turns, last, over, talk = [], [], False, 0, created, None, 0
|
||||
names = {}
|
||||
for x in lines[1:]:
|
||||
if x["t"] == "seat":
|
||||
(bots_seated if x["bot"] else humans).append(x["name"])
|
||||
names[x["id"]] = x["name"]
|
||||
elif x["t"] == "unseat":
|
||||
if x["id"] in names: bots_seated.remove(names.pop(x["id"]))
|
||||
elif x["t"] == "start":
|
||||
started = True
|
||||
elif x["t"] == "turn":
|
||||
turns += 1
|
||||
last = datetime.datetime.fromtimestamp(x["at"] / 1000, datetime.timezone.utc)
|
||||
elif x["t"] == "chat":
|
||||
talk += 1
|
||||
elif x["t"] == "over":
|
||||
over = "a draw" if x["winner"] is None else "%s won" % names.get(x["winner"], x["winner"])
|
||||
for h in humans:
|
||||
if h not in first_seen or created < first_seen[h]:
|
||||
first_seen[h] = created
|
||||
rooms.append(dict(id=meta["id"], size=meta["seats"], created=created, humans=humans, bots=bots_seated, started=started, turns=turns, last=last, over=over, talk=talk))
|
||||
|
||||
today = sorted([r for r in rooms if r["created"].strftime("%F") == day], key=lambda r: r["created"])
|
||||
names = sorted({h for r in today for h in r["humans"]})
|
||||
new = [n for n in names if first_seen[n].strftime("%F") == day]
|
||||
print("players today: %s | NEW today: %s" % (", ".join(names) or "none", ", ".join(new) or "none"))
|
||||
print("rooms today: %d (of %d ever)" % (len(today), len(rooms)))
|
||||
for r in today:
|
||||
idle_h = (now - r["last"]).total_seconds() / 3600
|
||||
state = "lobby only" if not r["started"] else ("started, no turns" if not r["turns"] else (r["over"] or ("abandoned" if idle_h > 12 else "in play")))
|
||||
span = ("%d min" % round((r["last"] - r["created"]).total_seconds() / 60)) if r["turns"] else ""
|
||||
seated = "+".join(r["humans"]) + ((" vs %d bot%s" % (len(r["bots"]), "" if len(r["bots"]) == 1 else "s")) if r["bots"] else "")
|
||||
empty = r["size"] - len(r["humans"]) - len(r["bots"])
|
||||
print(" %s %s %s%s %s %d turns %s last turn %s%s" % (
|
||||
r["created"].strftime("%H:%M"), r["id"], seated, (" (%d seat%s empty)" % (empty, "" if empty == 1 else "s")) if empty else "",
|
||||
state, r["turns"], span, r["last"].strftime("%H:%M") if r["turns"] else "never",
|
||||
(" %d line%s of talk" % (r["talk"], "" if r["talk"] == 1 else "s")) if r["talk"] else ""))
|
||||
PY
|
||||
Reference in New Issue
Block a user