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.
- atlantic_games-0.1.0/LICENSE +21 -0
- atlantic_games-0.1.0/PKG-INFO +365 -0
- atlantic_games-0.1.0/README.md +312 -0
- atlantic_games-0.1.0/pyproject.toml +79 -0
- atlantic_games-0.1.0/setup.cfg +4 -0
- atlantic_games-0.1.0/src/atlantic_games.egg-info/PKG-INFO +365 -0
- atlantic_games-0.1.0/src/atlantic_games.egg-info/SOURCES.txt +52 -0
- atlantic_games-0.1.0/src/atlantic_games.egg-info/dependency_links.txt +1 -0
- atlantic_games-0.1.0/src/atlantic_games.egg-info/entry_points.txt +3 -0
- atlantic_games-0.1.0/src/atlantic_games.egg-info/requires.txt +31 -0
- atlantic_games-0.1.0/src/atlantic_games.egg-info/top_level.txt +1 -0
- atlantic_games-0.1.0/src/atlgames/__init__.py +32 -0
- atlantic_games-0.1.0/src/atlgames/amuse.py +152 -0
- atlantic_games-0.1.0/src/atlgames/api.py +844 -0
- atlantic_games-0.1.0/src/atlgames/auth.py +146 -0
- atlantic_games-0.1.0/src/atlgames/bracket_city.py +177 -0
- atlantic_games-0.1.0/src/atlgames/cli/__init__.py +37 -0
- atlantic_games-0.1.0/src/atlgames/cli/__main__.py +4 -0
- atlantic_games-0.1.0/src/atlgames/cli/app.py +170 -0
- atlantic_games-0.1.0/src/atlgames/cli/archive.py +230 -0
- atlantic_games-0.1.0/src/atlgames/cli/config.py +104 -0
- atlantic_games-0.1.0/src/atlgames/cli/dates.py +73 -0
- atlantic_games-0.1.0/src/atlgames/cli/output.py +177 -0
- atlantic_games-0.1.0/src/atlgames/cli/play.py +195 -0
- atlantic_games-0.1.0/src/atlgames/cli/player.py +293 -0
- atlantic_games-0.1.0/src/atlgames/cli/puzzles.py +464 -0
- atlantic_games-0.1.0/src/atlgames/cli/settings.py +244 -0
- atlantic_games-0.1.0/src/atlgames/cli/state.py +53 -0
- atlantic_games-0.1.0/src/atlgames/cli/views.py +219 -0
- atlantic_games-0.1.0/src/atlgames/client.py +517 -0
- atlantic_games-0.1.0/src/atlgames/exceptions.py +76 -0
- atlantic_games-0.1.0/src/atlgames/formats.py +492 -0
- atlantic_games-0.1.0/src/atlgames/mcp_cli.py +13 -0
- atlantic_games-0.1.0/src/atlgames/mcp_server.py +703 -0
- atlantic_games-0.1.0/src/atlgames/models.py +508 -0
- atlantic_games-0.1.0/src/atlgames/py.typed +0 -0
- atlantic_games-0.1.0/src/atlgames/stacks.py +96 -0
- atlantic_games-0.1.0/src/atlgames/stats.py +123 -0
- atlantic_games-0.1.0/src/atlgames/structure.py +109 -0
- atlantic_games-0.1.0/src/atlgames/transport.py +232 -0
- atlantic_games-0.1.0/tests/test_amuse.py +142 -0
- atlantic_games-0.1.0/tests/test_api.py +383 -0
- atlantic_games-0.1.0/tests/test_auth.py +132 -0
- atlantic_games-0.1.0/tests/test_bracket_city.py +141 -0
- atlantic_games-0.1.0/tests/test_cli.py +539 -0
- atlantic_games-0.1.0/tests/test_client.py +781 -0
- atlantic_games-0.1.0/tests/test_formats.py +228 -0
- atlantic_games-0.1.0/tests/test_live.py +84 -0
- atlantic_games-0.1.0/tests/test_mcp.py +304 -0
- atlantic_games-0.1.0/tests/test_models.py +203 -0
- atlantic_games-0.1.0/tests/test_stacks.py +94 -0
- atlantic_games-0.1.0/tests/test_stats.py +92 -0
- atlantic_games-0.1.0/tests/test_structure.py +79 -0
- 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
|
+
[](https://github.com/lukwam/atlantic-games/actions/workflows/test.yml)
|
|
57
|
+
[](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.
|