Daily Bluesky bot for AT Mot. Invites players and congratulates yesterday's solvers.
1# atmot-bot
2
3Daily Bluesky bot for [AT Mot](https://atmot.herve.bzh). Posts once per language
4each day just after the UTC puzzle rollover: invites players to today's puzzle and
5congratulates yesterday's solvers (by count only).
6
7Runs as a stateless Cloudflare Worker with two Cron Triggers (EN 00:10 UTC, FR
800:11 UTC). Counts are read from the public Constellation backlink index; there is
9no database. The bot duplicates a small set of the app's frozen constants
10(`src/config.ts`) rather than importing the app.
11
12## Develop
13
14```sh
15npm install
16npm test # vitest — composer + facets + puzzle math
17npm run typecheck
18npm run dev # wrangler dev --test-scheduled (see Dry run below)
19```
20
21## Dry run (no real post)
22
23Create a gitignored `.dev.vars`:
24
25```
26DRY_RUN = "1"
27ATMOT_BOT_IDENTIFIER = "atmot.herve.bzh"
28ATMOT_BOT_APP_PASSWORD = "dry-run-unused"
29```
30
31Then `npm run dev` and, in another shell:
32
33```sh
34curl "http://localhost:8787/__scheduled?cron=10+0+*+*+*" # EN
35curl "http://localhost:8787/__scheduled?cron=11+0+*+*+*" # FR
36```
37
38The composed post is logged instead of published.
39
40## Deploy
41
42The bot posts as **@atmot.herve.bzh** using a Bluesky **app password** (Settings →
43Privacy and security → App passwords — not the account password).
44
45```sh
46npx wrangler login
47npx wrangler secret put ATMOT_BOT_IDENTIFIER # e.g. atmot.herve.bzh
48npx wrangler secret put ATMOT_BOT_APP_PASSWORD # the app password
49npm run deploy
50```
51
52The free Workers plan is sufficient: each language runs in its own scheduled
53invocation, so each gets the full 50-subrequest budget. Solver counting samples up
54to `SOLVER_SAMPLE_CAP` (20) records per language; beyond that the count is hedged
55(e.g. "20+").
56
57Each sampled record costs ~2 subrequests (DID-document resolution + the record
58read), so the default cap of 20 already uses most of the per-invocation budget on
59the free plan — keep headroom for the count, session, idempotency check, and
60publish (~6 more). On the paid plan you can raise `SOLVER_SAMPLE_CAP` in
61`src/config.ts`, but size it against the subrequest cost (~2 per record), not just
62the desired sample size.