Files
game-kit/template/deploy/sentry-slack-alert.sh
Eric WagonerandClaude Fable 5.1 33ac0ec808 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
2026-09-23 20:12:37 -04:00

77 lines
3.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Route every __NAME__ issue to a Slack channel: new issues (any level or
# priority), regressions, and reappearances. Creates one Sentry workflow
# via the alerts API. The token never leaves your shell:
# SENTRY_TOKEN=sntryu_... SLACK_CHANNEL_ID=C... deploy/sentry-slack-alert.sh [#channel]
# SLACK_CHANNEL_ID is the channel's Slack ID; the name defaults to
# #__SLUG__-notifications and is shown in Sentry beside the ID.
# The token needs the alerts:write scope (an org auth token from
# https://locallygrownnet.sentry.io/settings/auth-tokens/), and Slack must
# already be connected to the org under Settings → Integrations.
set -euo pipefail
: "${SENTRY_TOKEN:?SENTRY_TOKEN=sntryu_... is required (alerts:write)}"
ORG="${SENTRY_ORG:-locallygrownnet}"
PROJECT="${SENTRY_PROJECT:-__SLUG__}"
CHANNEL="${1:-#__SLUG__-notifications}"
API="https://us.sentry.io/api/0/organizations/$ORG"
AUTH="Authorization: Bearer $SENTRY_TOKEN"
# The Slack action takes the channel's ID as well as its name; the
# project's error detector is looked up by project id; a workflow is bound
# to its detectors by PUT after creation.
SLACK_ID="${SENTRY_SLACK_INTEGRATION:-__SENTRY_SLACK_INTEGRATION__}"
: "${SLACK_CHANNEL_ID:?SLACK_CHANNEL_ID=C... is required (the Slack ID of the channel)}"
CHANNEL_ID="$SLACK_CHANNEL_ID"
PROJECT_ID="${SENTRY_PROJECT_ID:-__SENTRY_PROJECT_ID__}"
echo "Slack integration $SLACK_ID; project $PROJECT_ID; channel $CHANNEL ($CHANNEL_ID)"
DETECTOR_ID=$(curl -s "$API/detectors/?project=$PROJECT_ID" -H "$AUTH" | python3 -c '
import json, sys
rows = json.load(sys.stdin)
for d in (rows if isinstance(rows, list) else []):
if str(d.get("projectId")) == sys.argv[1] and d.get("type") == "error": print(d["id"]); break' "$PROJECT_ID")
[ -n "$DETECTOR_ID" ] || { echo "no error detector for project $PROJECT_ID" >&2; exit 1; }
PAYLOAD=$(CHANNEL="$CHANNEL" CHANNEL_ID="$CHANNEL_ID" SLACK_ID="$SLACK_ID" DETECTOR_ID="$DETECTOR_ID" python3 -c '
import json, os
print(json.dumps({
"name": "__NAME__ → Slack",
"enabled": True,
"environment": None,
"config": {"frequency": 0},
"triggers": {
"logicType": "any-short",
"conditions": [
{"type": "first_seen_event", "comparison": True, "conditionResult": True},
{"type": "regression_event", "comparison": True, "conditionResult": True},
{"type": "reappeared_event", "comparison": True, "conditionResult": True},
],
"actions": [],
},
"actionFilters": [{
"logicType": "all",
"conditions": [],
"actions": [{
"type": "slack",
"integrationId": int(os.environ["SLACK_ID"]),
"data": {},
"config": {"targetType": "specific", "targetIdentifier": os.environ["CHANNEL_ID"], "targetDisplay": os.environ["CHANNEL"]},
"status": "active",
}],
}],
"detectorIds": [int(os.environ["DETECTOR_ID"])],
}))')
echo "creating the workflow…"
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$API/workflows/" -H "$AUTH" -H "Content-Type: application/json" -d "$PAYLOAD")
CODE=$(printf '%s' "$RESPONSE" | tail -1)
BODY=$(printf '%s' "$RESPONSE" | sed '$d')
if [ "$CODE" != "201" ]; then
echo "Sentry answered $CODE:" >&2; echo "$BODY" >&2; exit 1
fi
WORKFLOW_ID=$(printf '%s' "$BODY" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("id",""))')
echo "created workflow $WORKFLOW_ID"
# Binding to the detector is done by PUT, the whole workflow resent.
curl -s -o /dev/null -w "bound to detector $DETECTOR_ID: %{http_code}\n" -X PUT "$API/workflows/$WORKFLOW_ID/" -H "$AUTH" -H "Content-Type: application/json" -d "$PAYLOAD"
echo "done: https://$ORG.sentry.io/monitors/alerts/$WORKFLOW_ID/"