atlantic-games 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. atlantic_games-0.1.0/LICENSE +21 -0
  2. atlantic_games-0.1.0/PKG-INFO +365 -0
  3. atlantic_games-0.1.0/README.md +312 -0
  4. atlantic_games-0.1.0/pyproject.toml +79 -0
  5. atlantic_games-0.1.0/setup.cfg +4 -0
  6. atlantic_games-0.1.0/src/atlantic_games.egg-info/PKG-INFO +365 -0
  7. atlantic_games-0.1.0/src/atlantic_games.egg-info/SOURCES.txt +52 -0
  8. atlantic_games-0.1.0/src/atlantic_games.egg-info/dependency_links.txt +1 -0
  9. atlantic_games-0.1.0/src/atlantic_games.egg-info/entry_points.txt +3 -0
  10. atlantic_games-0.1.0/src/atlantic_games.egg-info/requires.txt +31 -0
  11. atlantic_games-0.1.0/src/atlantic_games.egg-info/top_level.txt +1 -0
  12. atlantic_games-0.1.0/src/atlgames/__init__.py +32 -0
  13. atlantic_games-0.1.0/src/atlgames/amuse.py +152 -0
  14. atlantic_games-0.1.0/src/atlgames/api.py +844 -0
  15. atlantic_games-0.1.0/src/atlgames/auth.py +146 -0
  16. atlantic_games-0.1.0/src/atlgames/bracket_city.py +177 -0
  17. atlantic_games-0.1.0/src/atlgames/cli/__init__.py +37 -0
  18. atlantic_games-0.1.0/src/atlgames/cli/__main__.py +4 -0
  19. atlantic_games-0.1.0/src/atlgames/cli/app.py +170 -0
  20. atlantic_games-0.1.0/src/atlgames/cli/archive.py +230 -0
  21. atlantic_games-0.1.0/src/atlgames/cli/config.py +104 -0
  22. atlantic_games-0.1.0/src/atlgames/cli/dates.py +73 -0
  23. atlantic_games-0.1.0/src/atlgames/cli/output.py +177 -0
  24. atlantic_games-0.1.0/src/atlgames/cli/play.py +195 -0
  25. atlantic_games-0.1.0/src/atlgames/cli/player.py +293 -0
  26. atlantic_games-0.1.0/src/atlgames/cli/puzzles.py +464 -0
  27. atlantic_games-0.1.0/src/atlgames/cli/settings.py +244 -0
  28. atlantic_games-0.1.0/src/atlgames/cli/state.py +53 -0
  29. atlantic_games-0.1.0/src/atlgames/cli/views.py +219 -0
  30. atlantic_games-0.1.0/src/atlgames/client.py +517 -0
  31. atlantic_games-0.1.0/src/atlgames/exceptions.py +76 -0
  32. atlantic_games-0.1.0/src/atlgames/formats.py +492 -0
  33. atlantic_games-0.1.0/src/atlgames/mcp_cli.py +13 -0
  34. atlantic_games-0.1.0/src/atlgames/mcp_server.py +703 -0
  35. atlantic_games-0.1.0/src/atlgames/models.py +508 -0
  36. atlantic_games-0.1.0/src/atlgames/py.typed +0 -0
  37. atlantic_games-0.1.0/src/atlgames/stacks.py +96 -0
  38. atlantic_games-0.1.0/src/atlgames/stats.py +123 -0
  39. atlantic_games-0.1.0/src/atlgames/structure.py +109 -0
  40. atlantic_games-0.1.0/src/atlgames/transport.py +232 -0
  41. atlantic_games-0.1.0/tests/test_amuse.py +142 -0
  42. atlantic_games-0.1.0/tests/test_api.py +383 -0
  43. atlantic_games-0.1.0/tests/test_auth.py +132 -0
  44. atlantic_games-0.1.0/tests/test_bracket_city.py +141 -0
  45. atlantic_games-0.1.0/tests/test_cli.py +539 -0
  46. atlantic_games-0.1.0/tests/test_client.py +781 -0
  47. atlantic_games-0.1.0/tests/test_formats.py +228 -0
  48. atlantic_games-0.1.0/tests/test_live.py +84 -0
  49. atlantic_games-0.1.0/tests/test_mcp.py +304 -0
  50. atlantic_games-0.1.0/tests/test_models.py +203 -0
  51. atlantic_games-0.1.0/tests/test_stacks.py +94 -0
  52. atlantic_games-0.1.0/tests/test_stats.py +92 -0
  53. atlantic_games-0.1.0/tests/test_structure.py +79 -0
  54. atlantic_games-0.1.0/tests/test_transport.py +272 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lukas Karlsson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,365 @@
1
+ Metadata-Version: 2.4
2
+ Name: atlantic-games
3
+ Version: 0.1.0
4
+ Summary: Unofficial Python client for The Atlantic's games: Bracket City, the Crossword, Caleb's Inferno, Rabbithole, Trivia and Stacks
5
+ Author-email: Lukas Karlsson <lukwam@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/lukwam/atlantic-games
8
+ Project-URL: Issues, https://github.com/lukwam/atlantic-games/issues
9
+ Keywords: atlantic,the-atlantic,games,bracket-city,crossword,rabbithole,trivia,amuse-labs,puzzleme,puzzles
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Games/Entertainment :: Puzzle Games
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Framework :: AsyncIO
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: httpx>=0.27
28
+ Requires-Dist: pydantic>=2.8
29
+ Provides-Extra: api
30
+ Requires-Dist: fastapi>=0.111; extra == "api"
31
+ Requires-Dist: uvicorn>=0.30; extra == "api"
32
+ Provides-Extra: mcp
33
+ Requires-Dist: mcp>=2.3; extra == "mcp"
34
+ Provides-Extra: cli
35
+ Requires-Dist: pyyaml>=6; extra == "cli"
36
+ Requires-Dist: rich>=13; extra == "cli"
37
+ Requires-Dist: typer>=0.12; extra == "cli"
38
+ Requires-Dist: tzdata; sys_platform == "win32" and extra == "cli"
39
+ Provides-Extra: test
40
+ Requires-Dist: fastapi>=0.111; extra == "test"
41
+ Requires-Dist: ipuz; extra == "test"
42
+ Requires-Dist: puzpy; extra == "test"
43
+ Requires-Dist: pytest; extra == "test"
44
+ Requires-Dist: pyyaml>=6; extra == "test"
45
+ Requires-Dist: pytest-asyncio; extra == "test"
46
+ Requires-Dist: pytest-cov; extra == "test"
47
+ Requires-Dist: respx; extra == "test"
48
+ Requires-Dist: rich>=13; extra == "test"
49
+ Requires-Dist: typer>=0.12; extra == "test"
50
+ Requires-Dist: xmlschema; extra == "test"
51
+ Requires-Dist: mcp>=2.3; extra == "test"
52
+ Dynamic: license-file
53
+
54
+ # atlantic-games
55
+
56
+ [![Test](https://github.com/lukwam/atlantic-games/actions/workflows/test.yml/badge.svg)](https://github.com/lukwam/atlantic-games/actions/workflows/test.yml)
57
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/lukwam/atlantic-games/blob/main/LICENSE)
58
+
59
+ An unofficial Python client for The Atlantic's games: Bracket City, the
60
+ Crossword, Caleb's Inferno and Rabbithole, plus the archives of Atlantic
61
+ Trivia and Stacks, and your own saved games and stats.
62
+
63
+ The Atlantic doesn't publish documentation for these. This package wraps
64
+ them in one client, sync and async, with typed
65
+ [Pydantic](https://docs.pydantic.dev/) models. Optional extras add the `atlg`
66
+ command line tool, an MCP server for AI agents, and a REST API. It's the
67
+ sibling of [nytimes-games](https://github.com/lukwam/nytimes-games) and works
68
+ the same way.
69
+
70
+ The crosswords are hosted by [Amuse Labs](https://amuselabs.com)' PuzzleMe,
71
+ which does a great job of putting puzzles in front of more people on more
72
+ sites. Thanks to them.
73
+
74
+ ## Installation
75
+
76
+ ```bash
77
+ pip install atlantic-games
78
+ ```
79
+
80
+ The package installs as `atlantic-games` and is imported as `atlgames`.
81
+ Python 3.10 or newer is required.
82
+
83
+ ## Quick start
84
+
85
+ ```python
86
+ from atlgames import AtlanticGamesClient
87
+
88
+ client = AtlanticGamesClient()
89
+
90
+ client.bracket_city("2026-10-08").solved_text # "Raytheon patents the microwave"
91
+ client.crossword("2026-10-10").entries() # clues, answers, crossings
92
+ client.crossword(series="inferno") # the latest Caleb's Inferno
93
+ client.rabbithole().questions # the latest Rabbithole chain
94
+ ```
95
+
96
+ The async client has the same methods:
97
+
98
+ ```python
99
+ from atlgames import AsyncAtlanticGamesClient
100
+
101
+ async with AsyncAtlanticGamesClient() as client:
102
+ puzzle = await client.bracket_city()
103
+ ```
104
+
105
+ Your saved games need your Atlantic login cookie:
106
+
107
+ ```python
108
+ from atlgames.stats import bracket_city_stats
109
+
110
+ client = AtlanticGamesClient(cookies="atljwt6=...")
111
+
112
+ client.bracket_city_game("2026-10-11").data.totalKeystrokes
113
+ bracket_city_stats(client.bracket_city_games()).longest_streak
114
+ ```
115
+
116
+ ## What's available
117
+
118
+ Puzzles don't need cookies. Everything about you does.
119
+
120
+ ### Puzzles
121
+
122
+ | Game | Methods | Available | Spoilers |
123
+ |---|---|---|---|
124
+ | Bracket City | `bracket_city(date=None)`, `bracket_city_dates()` | 2025-01-01, daily | `solutions`, `solved_text` |
125
+ | Crossword | `crossword(date=None, puzzle_id=None)`, `crosswords(date_start, date_end, limit)` | 2018-09-24, daily | the grid (`box`) and `placedWords` answers |
126
+ | Caleb's Inferno | the same, with `series="inferno"` | 2022 to 2026-06, monthly | as for the Crossword |
127
+ | Rabbithole | `rabbithole(puzzle_id=None)`, `rabbithole_puzzles(date_start, date_end)` | 2026-08-12, weekdays | `canonicalAnswer`, `validAnswers` |
128
+ | Atlantic Trivia | `trivia(article)`, `trivia_articles(limit)` | 2025-10-03 to 2026-08-14 (ended) | answers, hints and explanations |
129
+ | Stacks | `stacks(date)`, `stacks_puzzles()` | 2025-06-17 to 2026-08-24 (ended) | `solution` |
130
+ | Games hub | `games()`, `game(slug)` | | |
131
+
132
+ Dates are `YYYY-MM-DD` strings (or `datetime.date`) in New York time, which
133
+ is when the puzzles change. With no date you get the latest puzzle.
134
+ Unpublished puzzles aren't available early.
135
+
136
+ ### You (needs your `atljwt6` cookie)
137
+
138
+ | Data | Method | What you get |
139
+ |---|---|---|
140
+ | Account | `client.account` | Your user ID, when your login expires and your subscription flags, read from the cookies (no request) |
141
+ | Bracket City | `bracket_city_game(date)`, `bracket_city_games(date_start, date_end)` | Solved clues, keystrokes against the minimum, wrong guesses, peeks, whether you solved it on the day |
142
+ | Rabbithole | `rabbithole_game(puzzle)`, `rabbithole_games(date_start, date_end)` | Each question's result and your score |
143
+ | Crossword | `crossword_play(puzzle_id, series)` | Your saved play from Amuse Labs: time, score, correct entries, checks and reveals |
144
+ | Any game | `game_state(slug, date, id)`, `game_states(keys)` | The raw saved game, 25 per request |
145
+ | Stats | `atlgames.stats.bracket_city_stats(games)`, `rabbithole_stats(games)` | Streaks, completion, keystroke efficiency, perfect and clean solves, scores |
146
+
147
+ The Atlantic has no stats endpoint, so stats are computed from your saved
148
+ games. Some things to know about saved games:
149
+
150
+ - **Old games may not be there yet.** The mobile app keeps your history on
151
+ the phone and only uploads a puzzle when you open it while logged in. Open
152
+ old dates in the app to bring them into your account.
153
+ - **They're snapshots.** The server keeps only the latest state of each game,
154
+ and it can go backward: the app's "reset puzzle" button, replaying, or
155
+ opening a date can replace a finished game with an empty one. Each result
156
+ has `fetched_at`; keep your own copies if your history matters to you.
157
+ - `solvedOnPubDate` was only recorded from about August 2025, so None means
158
+ unknown, not late.
159
+
160
+ The library is read-only. It never writes saved games.
161
+
162
+ ### Puzzle structure
163
+
164
+ ```python
165
+ puzzle = client.bracket_city("2026-10-11")
166
+
167
+ puzzle.clues() # every clue: its text, answer, depth and the clues inside it
168
+ puzzle.available() # the clues you can solve now
169
+ puzzle.render(["your + my", "ghost's exclamation"]) # the text with those clues solved
170
+ puzzle.steps() # an order that solves it, innermost first
171
+ puzzle.solved_text # the final text the game shows
172
+ puzzle.caption_matches # False for the ~1 in 30 puzzles whose puzzleSolution differs
173
+
174
+ crossword = client.crossword("2026-10-10")
175
+ crossword.grid() # rows of letters, None for black squares
176
+ crossword.entries() # "1-Across": clue, answer, (row, col) squares, crossings
177
+ crossword.squares()
178
+
179
+ question = client.rabbithole().questions[0]
180
+ question.accepts("mark zuckerburg") # Rabbithole's fuzzy answer matching
181
+
182
+ client.stacks("2025-06-17").rows() # the solved Stacks board
183
+ ```
184
+
185
+ ### Crossword files
186
+
187
+ Export crosswords to the formats other crossword apps use:
188
+
189
+ ```python
190
+ from atlgames import formats
191
+
192
+ puzzle = client.crossword("2026-10-10")
193
+ open("atlantic.puz", "wb").write(formats.to_puz(puzzle)) # Across Lite
194
+ formats.to_ipuz(puzzle) # ipuz, as a dict
195
+ formats.to_xml(puzzle) # Crossword Compiler XML
196
+ formats.export(puzzle, "ipuz") # any format, as bytes
197
+ formats.fidelity(puzzle, "puz") # what the format approximates
198
+ ```
199
+
200
+ Clue formatting such as italics is dropped. Puzzles a format can't represent
201
+ raise `AtlanticGamesExportError` with the reasons. Exported files are for
202
+ personal use: the puzzles are The Atlantic's.
203
+
204
+ ## Cookies
205
+
206
+ Log in at theatlantic.com, open your browser's developer tools, and copy the
207
+ `atljwt6` cookie for `.theatlantic.com` (Application tab in Chrome, Storage
208
+ tab in Firefox). It's httpOnly, so scripts on the page can't read it. Treat it
209
+ like a password. It lasts 30 days; the site renews it as you browse, so you'll
210
+ need to copy a fresh one about once a month.
211
+
212
+ `cookies` can be a `Cookie` header string, a dict, a Cookie-Editor JSON
213
+ export (as a list or a JSON string), or the bare `atljwt6` token. Only
214
+ `atljwt6` is needed; `atljwt` adds your subscription flags to `account`.
215
+
216
+ Cookies are only ever sent to The Atlantic's API, never to Amuse Labs or the
217
+ puzzle file servers, and they never appear in errors or logs.
218
+
219
+ Your crossword progress is stored by Amuse Labs under your Atlantic user ID.
220
+ That ID is read from your own cookie; the client has no way to ask for anyone
221
+ else's.
222
+
223
+ ## Errors
224
+
225
+ | Exception | When |
226
+ |---|---|
227
+ | `AtlanticGamesNotFoundError` | No puzzle for that date or ID, or not published yet |
228
+ | `AtlanticGamesAuthenticationError` | Your data was requested without a valid `atljwt6` cookie |
229
+ | `AtlanticGamesRateLimitError` | 429 after retrying; `retry_after` has the suggested wait |
230
+ | `AtlanticGamesGraphQLError` | The Atlantic's API returned other errors |
231
+ | `AtlanticGamesHTTPError` | Any other HTTP error (the base of the above) |
232
+ | `AtlanticGamesParseError` | A page didn't have the expected data, e.g. Amuse Labs changed its format |
233
+ | `AtlanticGamesExportError` | A crossword can't be saved in a file format |
234
+
235
+ Failed connections, rate limits and server errors are retried 3 times with
236
+ exponential backoff, honoring `Retry-After`. Adjust with
237
+ `AtlanticGamesClient(retries=5, backoff=1.0, timeout=60)`, or pass your own
238
+ `httpx.Client` / `httpx.AsyncClient` as `http_client`.
239
+
240
+ ## Command line
241
+
242
+ The `cli` extra installs `atlg`:
243
+
244
+ ```bash
245
+ pip install "atlantic-games[cli]"
246
+
247
+ atlg bracket-city # today's puzzle, no spoilers
248
+ atlg bracket-city 2026-10-08 --steps -a
249
+ atlg crossword yesterday --save crossword.puz
250
+ atlg crossword --series inferno
251
+ atlg rabbithole -a
252
+ atlg trivia last
253
+ atlg stacks 2025-06-17 -a
254
+ atlg list crossword --from 2026-10-01
255
+ atlg play bracket-city # play in the terminal (nothing is saved to your account)
256
+ atlg play rabbithole
257
+ ```
258
+
259
+ Answers are hidden unless you pass `--answers` / `-a`. Every command takes
260
+ `--format json|yaml|csv|table|value(FIELD,...)`, and dates can be
261
+ `YYYY-MM-DD`, `today`, `yesterday`, a weekday name, `first` or `last`.
262
+
263
+ ### Your stats and history
264
+
265
+ ```bash
266
+ atlg auth login --from-file cookies.json # a Cookie-Editor export, or paste atljwt6 when prompted
267
+ atlg auth status
268
+ atlg today
269
+ atlg stats
270
+ atlg history bracket-city --from 2026-01-01
271
+ ```
272
+
273
+ Cookies are stored in `~/.config/atlg/config.ini` (mode 0600), with profiles
274
+ for more than one account (`--profile`, `atlg config profiles`).
275
+ `ATLANTIC_COOKIES` overrides the stored login.
276
+
277
+ ### Archiving
278
+
279
+ ```bash
280
+ atlg archive bracket-city --from first --to today -o archive
281
+ atlg archive crossword --from 2026-01-01 -o archive --as puz
282
+ ```
283
+
284
+ Archives write one file per date with a manifest, and resume where they
285
+ left off.
286
+
287
+ ## MCP server for AI agents
288
+
289
+ The `mcp` extra installs `atlg-mcp`, a local
290
+ [Model Context Protocol](https://modelcontextprotocol.io) server, so agents
291
+ can play along: ask for a Bracket City nudge, check a Rabbithole answer, or
292
+ export today's crossword.
293
+
294
+ ```bash
295
+ # Claude Code
296
+ claude mcp add atlantic-games -- uvx --from "atlantic-games[mcp]" atlg-mcp
297
+ ```
298
+
299
+ For other clients, add a stdio server with the command `uvx` and the
300
+ arguments `--from atlantic-games[mcp] atlg-mcp`.
301
+
302
+ | Tools | What they do |
303
+ |---|---|
304
+ | `bracket_city`, `bracket_city_peek`, `check_bracket_city_answer` | The puzzle with the clues you can solve now, peeks, and answer checks |
305
+ | `crossword`, `check_crossword_answer`, `crossword_archive` | Grids and entries with clues, lengths and crossings |
306
+ | `rabbithole`, `check_rabbithole_answer`, `rabbithole_archive` | One question at a time, with hints and choices on request |
307
+ | `trivia`, `check_trivia_answer`, `trivia_archive`, `stacks`, `games` | The Trivia and Stacks archives, and the games hub |
308
+ | `account`, `bracket_city_history`, `bracket_city_stats`, `rabbithole_history`, `rabbithole_stats`, `crossword_progress` | Your data (need cookies) |
309
+ | `export_crossword` | Save a crossword as `.puz`, `.ipuz` or `.xml` |
310
+
311
+ - Answers are hidden unless the agent asks for them, and the server tells
312
+ agents to leave them hidden while you're solving.
313
+ - Your data uses the same login as `atlg` (`atlg auth login`, or
314
+ `ATLANTIC_COOKIES` in the server's environment).
315
+ - Every tool is read-only except `export_crossword`, which only writes the
316
+ file you ask for.
317
+
318
+ ## REST API
319
+
320
+ The `api` extra adds `atlgames.api`, a FastAPI app with every puzzle, your
321
+ saved games and stats, and crossword downloads:
322
+
323
+ ```bash
324
+ pip install "atlantic-games[api]"
325
+ uvicorn atlgames.api:app
326
+ ```
327
+
328
+ Open `/docs` for the full list. A few routes:
329
+
330
+ | Route | Returns |
331
+ |---|---|
332
+ | `GET /bracket-city/{date}` | A Bracket City puzzle |
333
+ | `GET /crosswords/{daily\|inferno}/{date}` | A crossword |
334
+ | `GET /crosswords/{series}/{date}/download?format=puz` | A crossword file |
335
+ | `GET /rabbithole/latest` | The latest Rabbithole |
336
+ | `GET /bracket-city/stats` | Your stats (send your `atljwt6` cookie) |
337
+
338
+ Requests use the `atljwt6` and `atljwt` cookies they're sent with; no other
339
+ cookies are passed on. Mount `atlgames.api.router` in your own app, or start
340
+ from [`examples/api`](examples/api) for a Docker container.
341
+
342
+ ## How it works
343
+
344
+ The Atlantic's games come from four places, none of which need a login for
345
+ puzzles:
346
+
347
+ - **Bracket City** puzzles are JSON files on CloudFront.
348
+ - **Crosswords** come from Amuse Labs' PuzzleMe pages. Their puzzle data is
349
+ lightly scrambled; the client unscrambles it and, if Amuse Labs changes
350
+ the scramble, reads the new one from their current JavaScript.
351
+ - **Rabbithole, Trivia, the games hub and your saved games** come from The
352
+ Atlantic's GraphQL API.
353
+ - **Stacks** ended, and all its puzzles ship inside the game's own script.
354
+
355
+ [docs/endpoints.md](docs/endpoints.md) has the details: endpoints, schemas and quirks.
356
+
357
+ ## Contributing
358
+
359
+ See [CONTRIBUTING.md](CONTRIBUTING.md). A daily live check catches upstream
360
+ changes.
361
+
362
+ ## License
363
+
364
+ MIT. This project isn't affiliated with The Atlantic or Amuse Labs. The
365
+ puzzles belong to The Atlantic and their constructors.