reseat 0.5.1__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.
- reseat-0.5.1/.gitignore +13 -0
- reseat-0.5.1/LICENSE +21 -0
- reseat-0.5.1/PKG-INFO +277 -0
- reseat-0.5.1/README.md +247 -0
- reseat-0.5.1/demo/README.md +29 -0
- reseat-0.5.1/demo/_harness.py +92 -0
- reseat-0.5.1/demo/book.py +36 -0
- reseat-0.5.1/demo/phone.py +98 -0
- reseat-0.5.1/demo/site.py +94 -0
- reseat-0.5.1/demo/swap.py +36 -0
- reseat-0.5.1/demo/watch.py +55 -0
- reseat-0.5.1/docs/api-facts.md +142 -0
- reseat-0.5.1/docs/architecture.md +146 -0
- reseat-0.5.1/docs/architecture.png +0 -0
- reseat-0.5.1/docs/article.md +116 -0
- reseat-0.5.1/docs/demo.gif +0 -0
- reseat-0.5.1/docs/openapi.json +2232 -0
- reseat-0.5.1/docs/proof/2026-10-08-catalog-empty.md +73 -0
- reseat-0.5.1/docs/proof/2026-10-09-catalog-check.md +82 -0
- reseat-0.5.1/docs/proof/README.md +38 -0
- reseat-0.5.1/docs/proof/_template.md +33 -0
- reseat-0.5.1/docs/screenshots/approve.png +0 -0
- reseat-0.5.1/docs/screenshots/dashboard.png +0 -0
- reseat-0.5.1/docs/screenshots/today.png +0 -0
- reseat-0.5.1/pyproject.toml +57 -0
- reseat-0.5.1/src/reseat/__init__.py +3 -0
- reseat-0.5.1/src/reseat/auth.py +265 -0
- reseat-0.5.1/src/reseat/campus.py +157 -0
- reseat-0.5.1/src/reseat/cancel.py +82 -0
- reseat-0.5.1/src/reseat/cli.py +801 -0
- reseat-0.5.1/src/reseat/client.py +346 -0
- reseat-0.5.1/src/reseat/config.py +51 -0
- reseat-0.5.1/src/reseat/demo.py +137 -0
- reseat-0.5.1/src/reseat/demo_catalog.json +19 -0
- reseat-0.5.1/src/reseat/fakeapi.py +428 -0
- reseat-0.5.1/src/reseat/favorites.py +148 -0
- reseat-0.5.1/src/reseat/fixtures.py +63 -0
- reseat-0.5.1/src/reseat/guard.py +267 -0
- reseat-0.5.1/src/reseat/mcp_server.py +312 -0
- reseat-0.5.1/src/reseat/models.py +214 -0
- reseat-0.5.1/src/reseat/pages.py +724 -0
- reseat-0.5.1/src/reseat/push.py +114 -0
- reseat-0.5.1/src/reseat/router.py +460 -0
- reseat-0.5.1/src/reseat/rules.py +437 -0
- reseat-0.5.1/src/reseat/serve.py +479 -0
- reseat-0.5.1/src/reseat/static/app.css +155 -0
- reseat-0.5.1/src/reseat/static/app.js +86 -0
- reseat-0.5.1/src/reseat/store.py +399 -0
- reseat-0.5.1/src/reseat/swap.py +345 -0
- reseat-0.5.1/src/reseat/watcher.py +671 -0
- reseat-0.5.1/tests/conftest.py +52 -0
- reseat-0.5.1/tests/fixtures/README.md +4 -0
- reseat-0.5.1/tests/fixtures/catalog-2026-10-01.json +2070 -0
- reseat-0.5.1/tests/fixtures/planner-export.sample.json +57 -0
- reseat-0.5.1/tests/test_campus.py +84 -0
- reseat-0.5.1/tests/test_cancel_and_cli.py +159 -0
- reseat-0.5.1/tests/test_cli_output.py +84 -0
- reseat-0.5.1/tests/test_client.py +187 -0
- reseat-0.5.1/tests/test_demos.py +88 -0
- reseat-0.5.1/tests/test_failure_paths.py +350 -0
- reseat-0.5.1/tests/test_failure_paths_phone.py +646 -0
- reseat-0.5.1/tests/test_fault_storm.py +224 -0
- reseat-0.5.1/tests/test_favorites.py +141 -0
- reseat-0.5.1/tests/test_fixtures.py +30 -0
- reseat-0.5.1/tests/test_guard.py +209 -0
- reseat-0.5.1/tests/test_mcp.py +294 -0
- reseat-0.5.1/tests/test_models.py +19 -0
- reseat-0.5.1/tests/test_real_catalog.py +72 -0
- reseat-0.5.1/tests/test_resilience.py +167 -0
- reseat-0.5.1/tests/test_router.py +268 -0
- reseat-0.5.1/tests/test_rules.py +105 -0
- reseat-0.5.1/tests/test_rules_files.py +277 -0
- reseat-0.5.1/tests/test_rules_from_schedule.py +434 -0
- reseat-0.5.1/tests/test_serve.py +399 -0
- reseat-0.5.1/tests/test_store.py +82 -0
- reseat-0.5.1/tests/test_swap.py +382 -0
- reseat-0.5.1/tests/test_sweep_guards.py +243 -0
- reseat-0.5.1/tests/test_sweep_guards_more.py +322 -0
- reseat-0.5.1/tests/test_watcher.py +344 -0
- reseat-0.5.1/tests/test_web.py +488 -0
- reseat-0.5.1/tests/test_writes_closed.py +357 -0
reseat-0.5.1/.gitignore
ADDED
reseat-0.5.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Harpreet S. Siddhu
|
|
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.
|
reseat-0.5.1/PKG-INFO
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: reseat
|
|
3
|
+
Version: 0.5.1
|
|
4
|
+
Summary: re:Seat keeps your AWS re:Invent seats: watches for freed seats and new repeats, books within your rules, swaps safely, and tells you when to leave.
|
|
5
|
+
Project-URL: Homepage, https://github.com/hsiddhu2/reseat
|
|
6
|
+
Project-URL: Demo, https://hsiddhu2.github.io/reseat/
|
|
7
|
+
Project-URL: Documentation, https://github.com/hsiddhu2/reseat/blob/main/docs/architecture.md
|
|
8
|
+
Project-URL: Issues, https://github.com/hsiddhu2/reseat/issues
|
|
9
|
+
Author: Harpreet S. Siddhu
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: aws,events-api,mcp,reinvent,schedule
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Requires-Dist: httpx>=0.27
|
|
15
|
+
Requires-Dist: keyring>=25
|
|
16
|
+
Requires-Dist: pydantic>=2.7
|
|
17
|
+
Requires-Dist: pyyaml>=6
|
|
18
|
+
Requires-Dist: rich>=13
|
|
19
|
+
Requires-Dist: typer>=0.12
|
|
20
|
+
Requires-Dist: tzdata>=2024.1; sys_platform == 'win32'
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: mcp<3,>=2.2; extra == 'dev'
|
|
23
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
27
|
+
Provides-Extra: mcp
|
|
28
|
+
Requires-Dist: mcp<3,>=2.2; extra == 'mcp'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# re:Seat
|
|
32
|
+
|
|
33
|
+
re:Seat keeps your AWS re:Invent seats after the plan is made. It watches the catalog all day, books a seat the moment one frees up or a new repeat sitting appears, swaps a held seat for a better one without losing either, and tells you when to leave and whether to queue. It runs on your laptop against the [AWS Events API](https://docs.aws.amazon.com/events/latest/devguide/what-is-events-api.html), and your phone is the remote.
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
**Try it without installing anything:** [the click-through demo](https://hsiddhu2.github.io/reseat/) is the web app on demo data, built from this code. **Run it live in two minutes:** `pip install -e .` then `reseat serve --demo`. See [Open the dashboard](#open-the-dashboard).
|
|
38
|
+
|
|
39
|
+
## The problem
|
|
40
|
+
|
|
41
|
+
Planners help you pick sessions. Attendees say the trouble starts after that:
|
|
42
|
+
|
|
43
|
+
- "Most things were full by 10:03am." (2022)
|
|
44
|
+
- "Some stuff that was full yesterday I was able to reserve right now." (2024)
|
|
45
|
+
- "The more popular sessions get 2-3x extra sessions once they get booked up." (2022)
|
|
46
|
+
- "Get in line and keep refreshing the app. Most sessions will have no shows." (2022)
|
|
47
|
+
- "I was the 50th person on the walk-up line. They only accepted 20." (2023)
|
|
48
|
+
|
|
49
|
+
Seats free up, repeats get added, rooms move. Catching that means refreshing the app all week. re:Seat does the refreshing and acts on what it finds, within rules you set.
|
|
50
|
+
|
|
51
|
+
## Open the dashboard
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install -e .
|
|
55
|
+
reseat serve --demo # a scripted week on a fake Events API. No sign-in. Open http://127.0.0.1:8491/
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Demo mode runs the real watcher, router and swap code against an in-process fake of the API, with a week of real sessions from the 1 October catalog. Within two minutes a seat opens and a swap waits for your approval, a new sitting is booked, and a held session changes room. Nothing is sent to AWS, nothing is read from the keychain, and nothing is written to `~/.reseat`. Every page says "Demo data".
|
|
59
|
+
|
|
60
|
+
With your own seats, `reseat serve` runs the watcher and the same web app at `http://127.0.0.1:8490/`, in one process.
|
|
61
|
+
|
|
62
|
+
| Dashboard, laptop width | Approve, phone width | Today, phone width |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
|  |  |  |
|
|
65
|
+
|
|
66
|
+
- **Dashboard** (`/`). What re:Seat has done for you, counted from the journal: seats booked, swaps verified, seats restored. Watching or paused, the last sweep and its session count, the next sweep, held and wanted counts, and ListSessions quota left. Swaps that need you, each with its checks from the last sweep: the target's band, a fallback, no overlap, and whether the rules allow it or ask first. A week grid of held, wanted, proposed and fallback sessions with leave-now strips, a badge where the walk is longer than the gap, and a note on a held session that changed room. Last changes and the journal.
|
|
67
|
+
- **Approve** (`/approve`). One proposal at a time: what you hold and what opened, side by side, the checks, and one button. Bookings within your rules happen at once and show here and under Last changes.
|
|
68
|
+
- **Today** (`/today`). How long until you leave for the next held session, with the walk and when the doors close. The rest of today, your own personal time included. Wanted sessions you do not hold, with queue-or-go advice, its basis, and any clash with a held session's walk.
|
|
69
|
+
|
|
70
|
+
It follows your system's light or dark setting. Approve and Dismiss send a plan id and nothing else. Pressing Swap now runs the same checked swap as `reseat swap`, with fresh reads. The CLI and the MCP server use the same engine.
|
|
71
|
+
|
|
72
|
+
## Install in three commands
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
git clone https://github.com/hsiddhu2/reseat && cd reseat
|
|
76
|
+
pip install -e .
|
|
77
|
+
reseat login
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Python 3.11 or newer. `login` opens your AWS Builder ID sign-in. Use the Builder ID your re:Invent registration is under: `reseat whoami` should then say `Registered for reinvent2026`. The tokens stay in your OS keychain and are sent only to `api.awsevents.com`. Then point re:Seat at the sessions you want:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
reseat sync # the catalog, about 9 calls
|
|
84
|
+
reseat rules from-schedule # your reserved sessions and favorites from the AWS portal
|
|
85
|
+
reseat favorites sync # mirror them into the official app
|
|
86
|
+
reseat book --dry-run # see the plan. Nothing is sent
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Your week with re:Seat
|
|
90
|
+
|
|
91
|
+
| When | What re:Seat does |
|
|
92
|
+
|---|---|
|
|
93
|
+
| Before writes open | Takes your targets from the schedule you built in the AWS portal, or from a [reinvent-planner.cloud](https://reinvent-planner.cloud) export, and mirrors them into your favorites. |
|
|
94
|
+
| The day API writes open | `reseat book` reserves your targets, scarcest formats first and then in your order, 10 per call, inside the quota. A full session falls back to its next sitting in the same run. Every write is read back. |
|
|
95
|
+
| Every day until the event | `reseat serve` sweeps the catalog every minute. A freed seat or a new repeat of a target is booked at once. A better sitting that clashes with a lower-priority hold becomes a swap proposal on the dashboard and your phone. Leave-now reminders go into your official schedule. |
|
|
96
|
+
| At re:Invent | The laptop stays in the hotel room. Your phone shows today's seats, a leave-now countdown and whether to queue. During the day the laptop checks your sessions every 20 seconds, so a no-show's seat can be booked while you stand in the walk-up line. |
|
|
97
|
+
|
|
98
|
+
## Safe swap
|
|
99
|
+
|
|
100
|
+
The API has no swap. To move from held session A to a better B at the same time, A must be cancelled before B can be reserved, and for that moment you hold neither. re:Seat cancels A only when a fresh read shows B open, B clashes with nothing else you hold, A has a fallback (its own open seat or another open sitting of A, read fresh), and you approved or allowed auto-swap for that target. If the cancel fails and A is still held, it stops. If B fails, it re-reserves A. If A is gone too, it tries each fallback once and tells you exactly what you hold. If a read-back fails, or writes close mid-swap, it stops and says so. One swap at a time, every step journaled.
|
|
101
|
+
|
|
102
|
+
## The phone remote
|
|
103
|
+
|
|
104
|
+
Sign-in only works on your own machine, so the token stays on the laptop and the laptop makes every call. The web app that `reseat serve` runs also works at phone width, over [Tailscale](https://tailscale.com): approve a swap, see when to leave, and see what is wanted today. Optional push through ntfy. Designed for a laptop left in the hotel room. See [Running it all week](#running-it-all-week).
|
|
105
|
+
|
|
106
|
+
## Why re:Seat runs on your machine
|
|
107
|
+
|
|
108
|
+
The Events API signs you in with OAuth and PKCE, using your own Builder ID, through a callback on a loopback port of the machine you sign in on. It has no hosted sign-in. A hosted re:Seat would have to hold other attendees' tokens, and those tokens can cancel their seats. So re:Seat runs on your laptop, keeps your token in the OS keychain, sends it only to `api.awsevents.com`, and your phone talks only to your laptop.
|
|
109
|
+
|
|
110
|
+
## What the API requires, and what re:Seat does
|
|
111
|
+
|
|
112
|
+
| The API says | re:Seat |
|
|
113
|
+
|---|---|
|
|
114
|
+
| Quotas are per operation per minute, and reserve counts each session | Keeps its own count per operation and never sends a batch larger than what is left. |
|
|
115
|
+
| A 200 on reserve can still carry per-session failures | Reads every `failed` entry, then reads back your schedule after every write. |
|
|
116
|
+
| Reserve and cancel are not safe to retry | Never retries a write. A failure is resolved by reading the schedule back. |
|
|
117
|
+
| 429 carries `Retry-After` | Waits that long, once. |
|
|
118
|
+
| 409 means writes are switched off | Stops writing, keeps every opening queued, resumes when writes open. |
|
|
119
|
+
| Seat availability is a band, not a count | Treats a move from `unavailable` to an open band as a freed seat. |
|
|
120
|
+
| There is no search | Pulls the whole catalog, about 9 calls, and works locally. |
|
|
121
|
+
| Personal time is UTC in 5-minute steps | Writes leave-now blocks exactly that way. |
|
|
122
|
+
|
|
123
|
+
## Seen live
|
|
124
|
+
|
|
125
|
+
On 8 October 2026, between 20:19 and 20:28 PDT, on the day writes were scheduled to open, the Events API answered GetSchedule but served no sessions:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
ListSessions -> 200, totalCount 0, items 0
|
|
129
|
+
GetSession <held session> -> 404 "No session was found with the requested id"
|
|
130
|
+
GetSchedule -> 200, reserved 14, favorites 16
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
re:Seat's sync read the empty answer as every session removed and emptied its local copy of the catalog. It sent no write, and nothing on the attendee's AWS schedule changed. Three fixes followed, each tested against the fake: a sweep that comes back empty, or would drop more than half the catalog without bringing back one of about the same size, is refused and the local copy kept, a sweep of about the same size whose ids are mostly new is recorded as a new baseline, and nothing is booked while a held session is missing from the local catalog. The record, with commands and output, is in [docs/proof/2026-10-08-catalog-empty.md](docs/proof/2026-10-08-catalog-empty.md). It shows nothing about booking through the API.
|
|
134
|
+
|
|
135
|
+
## Limits
|
|
136
|
+
|
|
137
|
+
- It manages seats you already chose. It does not recommend sessions or solve your schedule.
|
|
138
|
+
- Queue-or-go advice is a rule by session type, improved by seat band history once there is some. It is a heuristic, never a prediction.
|
|
139
|
+
- Walking times between venues are conservative estimates, not official figures. Wynn and Encore come from the API as one venue and are split by room name.
|
|
140
|
+
- The web app is plain HTTP. Use it over Tailscale, not open hotel Wi-Fi. It is access control, not a security product.
|
|
141
|
+
- It cannot help with the first rush when reserved seating opens in the portal, two days before the API opens.
|
|
142
|
+
- The MCP server tells an agent to ask before approving a change, but cannot check that it did.
|
|
143
|
+
|
|
144
|
+
## How it is tested
|
|
145
|
+
|
|
146
|
+
Every write path runs against an in-process fake of the API that produces each documented failure: partial bulk results, `sessionFull`, `scheduleConflict`, 409, 429 with `Retry-After`, 5xx, dropped connections and expired sign-in. A fault storm runs the whole flow for an hour on the real 2026 catalog with 30 percent of reserves full, a 429 every minute, a 503, fifteen minutes of 409, and the real quotas enforced, and checks that no quota is exceeded, no talk is held twice and every write is read back. CI runs the tests on Linux, macOS and Windows, on Python 3.11 and 3.13. Details in [How re:Seat works](docs/architecture.md).
|
|
147
|
+
|
|
148
|
+
## Reference
|
|
149
|
+
|
|
150
|
+
### Use
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
reseat events # list events, no sign-in needed
|
|
154
|
+
reseat sync # pull the whole catalog, about 9 API calls
|
|
155
|
+
reseat sync --no-abstracts # cheap sweep, reports band changes and new sessions
|
|
156
|
+
reseat sync --force # apply even an empty or much smaller catalog. Exit 4 means a sweep was refused
|
|
157
|
+
reseat search "serverless"
|
|
158
|
+
reseat show <sessionId> # details, repeats, seat band history
|
|
159
|
+
reseat schedule # your reserved, favorites, personal time
|
|
160
|
+
reseat favorite <id> <id> ... # add favorites, 10 per call, quota aware
|
|
161
|
+
reseat probe --session-id <id> # are reservation writes open yet? never changes anything
|
|
162
|
+
reseat save-fixture # dev: save a catalog pull for tests, no abstracts or speakers
|
|
163
|
+
reseat rules init # write a commented ~/.reseat/rules.yaml
|
|
164
|
+
reseat rules import export.json # targets from a reinvent-planner.cloud export, in its order
|
|
165
|
+
reseat rules from-schedule # targets from your official schedule: reserved first, then favorites
|
|
166
|
+
reseat rules check # every code resolves in the local catalog?
|
|
167
|
+
reseat book --dry-run # sweep, then print the plan. Sends nothing
|
|
168
|
+
reseat book # reserve, fall back on full sessions, read back. --yes skips the confirmation
|
|
169
|
+
reseat cancel <sessionId> # shows the seat band, asks first, reads back
|
|
170
|
+
reseat favorites sync # mirror every target sitting into favorites. Works before 8 October
|
|
171
|
+
reseat watch # sweep every minute, book freed seats and new repeats. --once for cron
|
|
172
|
+
reseat swap <held> <wanted> # replace a held session safely: fallback checked, rolls back on failure
|
|
173
|
+
reseat guard sync # leave-now blocks in your official schedule. --dry-run shows the diff
|
|
174
|
+
reseat serve # watcher plus the web app: dashboard, approve, today. See "Running it all week"
|
|
175
|
+
reseat serve --demo # the web app on a fake API with a scripted week. No sign-in
|
|
176
|
+
reseat mcp # local MCP server on stdio. Needs pip install -e ".[mcp]"
|
|
177
|
+
reseat logout
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Data lives in `~/.reseat/reseat.db`. Set `RESEAT_HOME` to move it. On macOS and Linux, re:Seat creates the folder readable by you only and the database the same way, and tightens an existing `~/.reseat`. A folder you name with `RESEAT_HOME` that already exists, or a symbolic link, keeps its own permissions.
|
|
181
|
+
|
|
182
|
+
### Rules file
|
|
183
|
+
|
|
184
|
+
`~/.reseat/rules.yaml` lists targets in priority order. re:Seat reserves nothing else. re:Seat writes it readable by you only, because it can hold `serve_secret`, and refuses to write through a symbolic link. If you made the file yourself and others on the machine can read a secret in it, re:Seat says so and gives the `chmod` to run.
|
|
185
|
+
|
|
186
|
+
If you built your schedule in the AWS portal, `reseat rules from-schedule` writes the file for you. Your reserved sessions come first, then your favorites, in the order the API lists them. The API does not promise that order, so reorder the targets to set your priority. Each target is the exact sitting you picked, by session id with `repeats: false`, so the file can be written even while the catalog is empty. `favorites sync` and `book` still need `reseat sync` first. Every reserved session is listed. A favorite past `watch_cap` is written as a comment, not dropped. The API treats a favorite as interest only, not a reservation. This command turns your favorites into booking targets on purpose, and says how many, so delete any you only want to keep an eye on. When an exact sitting is full and the talk has other sittings, `book` says so in one line. Nothing moves to another sitting unless you set `repeats: true` on that target. Other settings are the `rules init` defaults, with its example lunch left as a comment. Without `--force` it never replaces an existing file. With `--force` the old file is kept as `rules.yaml.bak`, because its settings, `serve_secret` included, are reset.
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
189
|
+
targets:
|
|
190
|
+
- code: ARC301 # any sitting of ARC301. Earliest first unless prefer: latest
|
|
191
|
+
backups: [ARC302] # tried if no sitting of ARC301 can be held
|
|
192
|
+
- session_id: 1780442277219001cKCh
|
|
193
|
+
repeats: false # this sitting only
|
|
194
|
+
meals:
|
|
195
|
+
- {day: Tuesday, start: "12:00", end: "13:00"}
|
|
196
|
+
max_per_day: 5
|
|
197
|
+
watch_cap: 25
|
|
198
|
+
home_venue: Venetian # where the first walk of each day starts
|
|
199
|
+
serve_secret: <long random string> # needed for the web app off this laptop
|
|
200
|
+
ntfy_topic: <random 16 to 64 characters> # optional push
|
|
201
|
+
probe_session: <id of a session that takes no reservations> # resume the moment writes open
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The first target wins a time slot. Within a batch, formats that are not recorded go first, because they fill first: Workshop, Lab and Bootcamp, then Builders' session, Chalk talk, Code talk, Breakout session, then the rest.
|
|
205
|
+
|
|
206
|
+
### Running it all week
|
|
207
|
+
|
|
208
|
+
re:Seat is designed for a laptop left in the hotel room, plugged in and awake, while you carry only your phone. Your sign-in can only happen on your own machine, so the laptop does every API call and the phone only talks to the laptop. Run `reseat serve` there. It runs the watcher and serves the web app: the dashboard, approve and today.
|
|
209
|
+
|
|
210
|
+
**Set up the phone.** Add a long random `serve_secret` to the rules file, then start it on the laptop with its Tailscale address (`tailscale ip -4` prints it):
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
reseat serve --host "$(tailscale ip -4)"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
It prints a one-time link. Open it once on each device. It sets a cookie and the address bar is left clean. If the cookie is lost, open `/login` on the same address and type the secret. A sign-in lasts 7 days. To sign every device out, stop and restart `reseat serve`: sign-ins live only in the running process. The page is plain HTTP, so open it over Tailscale, which encrypts the connection, not across open hotel Wi-Fi. Without `serve_secret`, `reseat serve` listens on 127.0.0.1 only and refuses any other address. It never listens on every interface: `--host 0.0.0.0` is refused. The page is access control for a hotel network, not a security product: it stops a neighbour on the same Wi-Fi from approving your swaps. Swap now and Dismiss take a plan id that expires after 10 minutes. No page or endpoint takes a session id.
|
|
217
|
+
|
|
218
|
+
**Keep the laptop awake with the lid closed.**
|
|
219
|
+
|
|
220
|
+
- macOS. `caffeinate -s reseat serve --host <Tailscale address>` keeps the Mac awake while it is plugged in. Closing the lid still puts most MacBooks to sleep unless an external display is attached. Either leave the lid open with the screen dimmed, or run `sudo pmset -a disablesleep 1` before you leave and `sudo pmset -a disablesleep 0` when you are back.
|
|
221
|
+
- Windows. Settings, System, Power and battery: when plugged in, sleep after Never. Control Panel, Power Options, Choose what closing the lid does: when plugged in, Do nothing.
|
|
222
|
+
|
|
223
|
+
**Reach it from the phone.** Install [Tailscale](https://tailscale.com) on the laptop and the phone and sign in to the same account on both. Start `reseat serve` with the laptop's Tailscale address and open the printed link on the phone. The phone then reaches the laptop from any venue, and the page is not offered on the hotel network.
|
|
224
|
+
|
|
225
|
+
**Push, if you want it.** Set `ntfy_topic` in the rules file to a long random name, install the ntfy app on the phone and subscribe to that topic. The laptop posts to `https://ntfy.sh/<topic>` when a seat is booked, a swap is proposed, done or rolled back, it is time to leave, the API has been unreachable for 10 minutes, it is back, or sign-in is needed. What leaves the laptop: the event type, session codes and titles, and those status lines. Never your token, an abstract, a session id or a plan id. Push is off unless the topic is set.
|
|
226
|
+
|
|
227
|
+
**When writes are switched off.** A 409 means the API has reservation writes turned off, and retrying will not help until they are back. re:Seat keeps watching and keeps every opening queued, but sends no reserve and runs no swap. It tries once after 15 minutes. With `probe_session` set to a session that takes no reservations, it checks every minute instead and resumes the moment writes open. Push says "Booking paused" and "Booking resumed".
|
|
228
|
+
|
|
229
|
+
**On site.** During the event days the laptop also checks the day's held and wanted sessions every 20 seconds, at most 40 of them, so a seat freed by a no-show is booked while you are in the walk-up line.
|
|
230
|
+
|
|
231
|
+
**When the hotel Wi-Fi drops.** The watcher keeps running. It retries with a growing wait, up to 5 minutes between tries, and goes back to its normal pace as soon as a sweep works. A sweep that fails halfway is not saved, so a seat that opened during the outage is still caught afterwards. Each outage goes in the journal. Your reserved seats stay reserved, because they live in your AWS schedule, not on the laptop. While the laptop is offline it cannot book anything and the phone cannot reach it, so use the official app until it is back. If the AWS API is down but the internet is up, push tells you "re:Seat offline since HH:MM" after 10 minutes and "re:Seat back" when it recovers.
|
|
232
|
+
|
|
233
|
+
**When sign-in expires.** If the token can no longer be refreshed, re:Seat stops booking, keeps watching for the moment it can read again, and tells you once: "sign in needed". Run `reseat login` on the laptop. Booking resumes on the next sweep.
|
|
234
|
+
|
|
235
|
+
### MCP server
|
|
236
|
+
|
|
237
|
+
`reseat mcp` runs a local MCP server over stdio, so an agent can read your targets and plan changes for you. Install the extra first: `pip install -e ".[mcp]"`. A client config looks like this, with the path to your `reseat` command:
|
|
238
|
+
|
|
239
|
+
```json
|
|
240
|
+
{
|
|
241
|
+
"mcpServers": {
|
|
242
|
+
"reseat": { "command": "/path/to/.venv/bin/reseat", "args": ["mcp"] }
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Tools: `list_targets`, `propose_changes`, `propose_swap`, `approve_changes`, `explain_drift`, `guard_sync`, `queue_or_go`. Every change is two steps. `propose_changes`, `propose_swap` or `guard_sync` returns a plan id and sends nothing. `propose_swap(held_code)` finds the open sitting of a held talk that your rules prefer to the one you hold, runs the swap's checks with fresh reads, and lists them. Approving it runs the same checked swap as `reseat swap`. `approve_changes` carries that plan out once, within 10 minutes, and reserves only what it named. A leave-now plan is refused if what you hold changed after it was shown. Calls run one at a time. No tool takes a list of session ids.
|
|
248
|
+
|
|
249
|
+
Know the limit: the server tells the agent to ask you before approving, but it cannot check that it did. The agent sees the plan id and could approve on its own. Session titles come from the catalog and reach the agent. Use a client that shows you each tool call before it runs.
|
|
250
|
+
|
|
251
|
+
### Good citizen rules
|
|
252
|
+
|
|
253
|
+
re:Seat only reserves what you asked for. It never holds two sittings of one talk. Watch lists are capped. It does not scrape or redistribute the catalog. Seats it frees during a swap go back to the pool at once.
|
|
254
|
+
|
|
255
|
+
### Development
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
pip install -e ".[dev]"
|
|
259
|
+
pytest
|
|
260
|
+
ruff check .
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
A fault storm runs the whole flow for an hour on the real catalog: 30 percent of reserves full, a 429 every minute, a 503, fifteen minutes of 409, quotas enforced. Tests run against an in-process fake of the API that produces every documented failure: `sessionFull`, `scheduleConflict` with `conflictsWith`, `alreadyScheduled`, 409 while writes are closed, 429 with `Retry-After`, and partial batch results. No network needed.
|
|
264
|
+
|
|
265
|
+
`tests/fixtures/catalog-2026-10-01.json` is a real catalog pull (no abstracts, no speaker names). `FakeEventsApi.from_fixture(path)` serves it, so tests see the real venue, room and type strings.
|
|
266
|
+
|
|
267
|
+
### Docs
|
|
268
|
+
|
|
269
|
+
- [How re:Seat works](docs/architecture.md): the design, each part marked built or planned, and how it is tested.
|
|
270
|
+
- [AWS Events API facts](docs/api-facts.md): the API behaviour re:Seat relies on, with sources.
|
|
271
|
+
- [Live proof](docs/proof/): checks run against the real API.
|
|
272
|
+
- [Demo scripts](demo/): recordable demos against the fake API. `reseat serve --demo` is the web app version.
|
|
273
|
+
- [Click-through demo](https://hsiddhu2.github.io/reseat/): the web app on demo data, rebuilt from `demo/site.py` on every push to main.
|
|
274
|
+
|
|
275
|
+
## License
|
|
276
|
+
|
|
277
|
+
MIT
|
reseat-0.5.1/README.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# re:Seat
|
|
2
|
+
|
|
3
|
+
re:Seat keeps your AWS re:Invent seats after the plan is made. It watches the catalog all day, books a seat the moment one frees up or a new repeat sitting appears, swaps a held seat for a better one without losing either, and tells you when to leave and whether to queue. It runs on your laptop against the [AWS Events API](https://docs.aws.amazon.com/events/latest/devguide/what-is-events-api.html), and your phone is the remote.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
**Try it without installing anything:** [the click-through demo](https://hsiddhu2.github.io/reseat/) is the web app on demo data, built from this code. **Run it live in two minutes:** `pip install -e .` then `reseat serve --demo`. See [Open the dashboard](#open-the-dashboard).
|
|
8
|
+
|
|
9
|
+
## The problem
|
|
10
|
+
|
|
11
|
+
Planners help you pick sessions. Attendees say the trouble starts after that:
|
|
12
|
+
|
|
13
|
+
- "Most things were full by 10:03am." (2022)
|
|
14
|
+
- "Some stuff that was full yesterday I was able to reserve right now." (2024)
|
|
15
|
+
- "The more popular sessions get 2-3x extra sessions once they get booked up." (2022)
|
|
16
|
+
- "Get in line and keep refreshing the app. Most sessions will have no shows." (2022)
|
|
17
|
+
- "I was the 50th person on the walk-up line. They only accepted 20." (2023)
|
|
18
|
+
|
|
19
|
+
Seats free up, repeats get added, rooms move. Catching that means refreshing the app all week. re:Seat does the refreshing and acts on what it finds, within rules you set.
|
|
20
|
+
|
|
21
|
+
## Open the dashboard
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install -e .
|
|
25
|
+
reseat serve --demo # a scripted week on a fake Events API. No sign-in. Open http://127.0.0.1:8491/
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Demo mode runs the real watcher, router and swap code against an in-process fake of the API, with a week of real sessions from the 1 October catalog. Within two minutes a seat opens and a swap waits for your approval, a new sitting is booked, and a held session changes room. Nothing is sent to AWS, nothing is read from the keychain, and nothing is written to `~/.reseat`. Every page says "Demo data".
|
|
29
|
+
|
|
30
|
+
With your own seats, `reseat serve` runs the watcher and the same web app at `http://127.0.0.1:8490/`, in one process.
|
|
31
|
+
|
|
32
|
+
| Dashboard, laptop width | Approve, phone width | Today, phone width |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
|  |  |  |
|
|
35
|
+
|
|
36
|
+
- **Dashboard** (`/`). What re:Seat has done for you, counted from the journal: seats booked, swaps verified, seats restored. Watching or paused, the last sweep and its session count, the next sweep, held and wanted counts, and ListSessions quota left. Swaps that need you, each with its checks from the last sweep: the target's band, a fallback, no overlap, and whether the rules allow it or ask first. A week grid of held, wanted, proposed and fallback sessions with leave-now strips, a badge where the walk is longer than the gap, and a note on a held session that changed room. Last changes and the journal.
|
|
37
|
+
- **Approve** (`/approve`). One proposal at a time: what you hold and what opened, side by side, the checks, and one button. Bookings within your rules happen at once and show here and under Last changes.
|
|
38
|
+
- **Today** (`/today`). How long until you leave for the next held session, with the walk and when the doors close. The rest of today, your own personal time included. Wanted sessions you do not hold, with queue-or-go advice, its basis, and any clash with a held session's walk.
|
|
39
|
+
|
|
40
|
+
It follows your system's light or dark setting. Approve and Dismiss send a plan id and nothing else. Pressing Swap now runs the same checked swap as `reseat swap`, with fresh reads. The CLI and the MCP server use the same engine.
|
|
41
|
+
|
|
42
|
+
## Install in three commands
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git clone https://github.com/hsiddhu2/reseat && cd reseat
|
|
46
|
+
pip install -e .
|
|
47
|
+
reseat login
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Python 3.11 or newer. `login` opens your AWS Builder ID sign-in. Use the Builder ID your re:Invent registration is under: `reseat whoami` should then say `Registered for reinvent2026`. The tokens stay in your OS keychain and are sent only to `api.awsevents.com`. Then point re:Seat at the sessions you want:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
reseat sync # the catalog, about 9 calls
|
|
54
|
+
reseat rules from-schedule # your reserved sessions and favorites from the AWS portal
|
|
55
|
+
reseat favorites sync # mirror them into the official app
|
|
56
|
+
reseat book --dry-run # see the plan. Nothing is sent
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Your week with re:Seat
|
|
60
|
+
|
|
61
|
+
| When | What re:Seat does |
|
|
62
|
+
|---|---|
|
|
63
|
+
| Before writes open | Takes your targets from the schedule you built in the AWS portal, or from a [reinvent-planner.cloud](https://reinvent-planner.cloud) export, and mirrors them into your favorites. |
|
|
64
|
+
| The day API writes open | `reseat book` reserves your targets, scarcest formats first and then in your order, 10 per call, inside the quota. A full session falls back to its next sitting in the same run. Every write is read back. |
|
|
65
|
+
| Every day until the event | `reseat serve` sweeps the catalog every minute. A freed seat or a new repeat of a target is booked at once. A better sitting that clashes with a lower-priority hold becomes a swap proposal on the dashboard and your phone. Leave-now reminders go into your official schedule. |
|
|
66
|
+
| At re:Invent | The laptop stays in the hotel room. Your phone shows today's seats, a leave-now countdown and whether to queue. During the day the laptop checks your sessions every 20 seconds, so a no-show's seat can be booked while you stand in the walk-up line. |
|
|
67
|
+
|
|
68
|
+
## Safe swap
|
|
69
|
+
|
|
70
|
+
The API has no swap. To move from held session A to a better B at the same time, A must be cancelled before B can be reserved, and for that moment you hold neither. re:Seat cancels A only when a fresh read shows B open, B clashes with nothing else you hold, A has a fallback (its own open seat or another open sitting of A, read fresh), and you approved or allowed auto-swap for that target. If the cancel fails and A is still held, it stops. If B fails, it re-reserves A. If A is gone too, it tries each fallback once and tells you exactly what you hold. If a read-back fails, or writes close mid-swap, it stops and says so. One swap at a time, every step journaled.
|
|
71
|
+
|
|
72
|
+
## The phone remote
|
|
73
|
+
|
|
74
|
+
Sign-in only works on your own machine, so the token stays on the laptop and the laptop makes every call. The web app that `reseat serve` runs also works at phone width, over [Tailscale](https://tailscale.com): approve a swap, see when to leave, and see what is wanted today. Optional push through ntfy. Designed for a laptop left in the hotel room. See [Running it all week](#running-it-all-week).
|
|
75
|
+
|
|
76
|
+
## Why re:Seat runs on your machine
|
|
77
|
+
|
|
78
|
+
The Events API signs you in with OAuth and PKCE, using your own Builder ID, through a callback on a loopback port of the machine you sign in on. It has no hosted sign-in. A hosted re:Seat would have to hold other attendees' tokens, and those tokens can cancel their seats. So re:Seat runs on your laptop, keeps your token in the OS keychain, sends it only to `api.awsevents.com`, and your phone talks only to your laptop.
|
|
79
|
+
|
|
80
|
+
## What the API requires, and what re:Seat does
|
|
81
|
+
|
|
82
|
+
| The API says | re:Seat |
|
|
83
|
+
|---|---|
|
|
84
|
+
| Quotas are per operation per minute, and reserve counts each session | Keeps its own count per operation and never sends a batch larger than what is left. |
|
|
85
|
+
| A 200 on reserve can still carry per-session failures | Reads every `failed` entry, then reads back your schedule after every write. |
|
|
86
|
+
| Reserve and cancel are not safe to retry | Never retries a write. A failure is resolved by reading the schedule back. |
|
|
87
|
+
| 429 carries `Retry-After` | Waits that long, once. |
|
|
88
|
+
| 409 means writes are switched off | Stops writing, keeps every opening queued, resumes when writes open. |
|
|
89
|
+
| Seat availability is a band, not a count | Treats a move from `unavailable` to an open band as a freed seat. |
|
|
90
|
+
| There is no search | Pulls the whole catalog, about 9 calls, and works locally. |
|
|
91
|
+
| Personal time is UTC in 5-minute steps | Writes leave-now blocks exactly that way. |
|
|
92
|
+
|
|
93
|
+
## Seen live
|
|
94
|
+
|
|
95
|
+
On 8 October 2026, between 20:19 and 20:28 PDT, on the day writes were scheduled to open, the Events API answered GetSchedule but served no sessions:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
ListSessions -> 200, totalCount 0, items 0
|
|
99
|
+
GetSession <held session> -> 404 "No session was found with the requested id"
|
|
100
|
+
GetSchedule -> 200, reserved 14, favorites 16
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
re:Seat's sync read the empty answer as every session removed and emptied its local copy of the catalog. It sent no write, and nothing on the attendee's AWS schedule changed. Three fixes followed, each tested against the fake: a sweep that comes back empty, or would drop more than half the catalog without bringing back one of about the same size, is refused and the local copy kept, a sweep of about the same size whose ids are mostly new is recorded as a new baseline, and nothing is booked while a held session is missing from the local catalog. The record, with commands and output, is in [docs/proof/2026-10-08-catalog-empty.md](docs/proof/2026-10-08-catalog-empty.md). It shows nothing about booking through the API.
|
|
104
|
+
|
|
105
|
+
## Limits
|
|
106
|
+
|
|
107
|
+
- It manages seats you already chose. It does not recommend sessions or solve your schedule.
|
|
108
|
+
- Queue-or-go advice is a rule by session type, improved by seat band history once there is some. It is a heuristic, never a prediction.
|
|
109
|
+
- Walking times between venues are conservative estimates, not official figures. Wynn and Encore come from the API as one venue and are split by room name.
|
|
110
|
+
- The web app is plain HTTP. Use it over Tailscale, not open hotel Wi-Fi. It is access control, not a security product.
|
|
111
|
+
- It cannot help with the first rush when reserved seating opens in the portal, two days before the API opens.
|
|
112
|
+
- The MCP server tells an agent to ask before approving a change, but cannot check that it did.
|
|
113
|
+
|
|
114
|
+
## How it is tested
|
|
115
|
+
|
|
116
|
+
Every write path runs against an in-process fake of the API that produces each documented failure: partial bulk results, `sessionFull`, `scheduleConflict`, 409, 429 with `Retry-After`, 5xx, dropped connections and expired sign-in. A fault storm runs the whole flow for an hour on the real 2026 catalog with 30 percent of reserves full, a 429 every minute, a 503, fifteen minutes of 409, and the real quotas enforced, and checks that no quota is exceeded, no talk is held twice and every write is read back. CI runs the tests on Linux, macOS and Windows, on Python 3.11 and 3.13. Details in [How re:Seat works](docs/architecture.md).
|
|
117
|
+
|
|
118
|
+
## Reference
|
|
119
|
+
|
|
120
|
+
### Use
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
reseat events # list events, no sign-in needed
|
|
124
|
+
reseat sync # pull the whole catalog, about 9 API calls
|
|
125
|
+
reseat sync --no-abstracts # cheap sweep, reports band changes and new sessions
|
|
126
|
+
reseat sync --force # apply even an empty or much smaller catalog. Exit 4 means a sweep was refused
|
|
127
|
+
reseat search "serverless"
|
|
128
|
+
reseat show <sessionId> # details, repeats, seat band history
|
|
129
|
+
reseat schedule # your reserved, favorites, personal time
|
|
130
|
+
reseat favorite <id> <id> ... # add favorites, 10 per call, quota aware
|
|
131
|
+
reseat probe --session-id <id> # are reservation writes open yet? never changes anything
|
|
132
|
+
reseat save-fixture # dev: save a catalog pull for tests, no abstracts or speakers
|
|
133
|
+
reseat rules init # write a commented ~/.reseat/rules.yaml
|
|
134
|
+
reseat rules import export.json # targets from a reinvent-planner.cloud export, in its order
|
|
135
|
+
reseat rules from-schedule # targets from your official schedule: reserved first, then favorites
|
|
136
|
+
reseat rules check # every code resolves in the local catalog?
|
|
137
|
+
reseat book --dry-run # sweep, then print the plan. Sends nothing
|
|
138
|
+
reseat book # reserve, fall back on full sessions, read back. --yes skips the confirmation
|
|
139
|
+
reseat cancel <sessionId> # shows the seat band, asks first, reads back
|
|
140
|
+
reseat favorites sync # mirror every target sitting into favorites. Works before 8 October
|
|
141
|
+
reseat watch # sweep every minute, book freed seats and new repeats. --once for cron
|
|
142
|
+
reseat swap <held> <wanted> # replace a held session safely: fallback checked, rolls back on failure
|
|
143
|
+
reseat guard sync # leave-now blocks in your official schedule. --dry-run shows the diff
|
|
144
|
+
reseat serve # watcher plus the web app: dashboard, approve, today. See "Running it all week"
|
|
145
|
+
reseat serve --demo # the web app on a fake API with a scripted week. No sign-in
|
|
146
|
+
reseat mcp # local MCP server on stdio. Needs pip install -e ".[mcp]"
|
|
147
|
+
reseat logout
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Data lives in `~/.reseat/reseat.db`. Set `RESEAT_HOME` to move it. On macOS and Linux, re:Seat creates the folder readable by you only and the database the same way, and tightens an existing `~/.reseat`. A folder you name with `RESEAT_HOME` that already exists, or a symbolic link, keeps its own permissions.
|
|
151
|
+
|
|
152
|
+
### Rules file
|
|
153
|
+
|
|
154
|
+
`~/.reseat/rules.yaml` lists targets in priority order. re:Seat reserves nothing else. re:Seat writes it readable by you only, because it can hold `serve_secret`, and refuses to write through a symbolic link. If you made the file yourself and others on the machine can read a secret in it, re:Seat says so and gives the `chmod` to run.
|
|
155
|
+
|
|
156
|
+
If you built your schedule in the AWS portal, `reseat rules from-schedule` writes the file for you. Your reserved sessions come first, then your favorites, in the order the API lists them. The API does not promise that order, so reorder the targets to set your priority. Each target is the exact sitting you picked, by session id with `repeats: false`, so the file can be written even while the catalog is empty. `favorites sync` and `book` still need `reseat sync` first. Every reserved session is listed. A favorite past `watch_cap` is written as a comment, not dropped. The API treats a favorite as interest only, not a reservation. This command turns your favorites into booking targets on purpose, and says how many, so delete any you only want to keep an eye on. When an exact sitting is full and the talk has other sittings, `book` says so in one line. Nothing moves to another sitting unless you set `repeats: true` on that target. Other settings are the `rules init` defaults, with its example lunch left as a comment. Without `--force` it never replaces an existing file. With `--force` the old file is kept as `rules.yaml.bak`, because its settings, `serve_secret` included, are reset.
|
|
157
|
+
|
|
158
|
+
```yaml
|
|
159
|
+
targets:
|
|
160
|
+
- code: ARC301 # any sitting of ARC301. Earliest first unless prefer: latest
|
|
161
|
+
backups: [ARC302] # tried if no sitting of ARC301 can be held
|
|
162
|
+
- session_id: 1780442277219001cKCh
|
|
163
|
+
repeats: false # this sitting only
|
|
164
|
+
meals:
|
|
165
|
+
- {day: Tuesday, start: "12:00", end: "13:00"}
|
|
166
|
+
max_per_day: 5
|
|
167
|
+
watch_cap: 25
|
|
168
|
+
home_venue: Venetian # where the first walk of each day starts
|
|
169
|
+
serve_secret: <long random string> # needed for the web app off this laptop
|
|
170
|
+
ntfy_topic: <random 16 to 64 characters> # optional push
|
|
171
|
+
probe_session: <id of a session that takes no reservations> # resume the moment writes open
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The first target wins a time slot. Within a batch, formats that are not recorded go first, because they fill first: Workshop, Lab and Bootcamp, then Builders' session, Chalk talk, Code talk, Breakout session, then the rest.
|
|
175
|
+
|
|
176
|
+
### Running it all week
|
|
177
|
+
|
|
178
|
+
re:Seat is designed for a laptop left in the hotel room, plugged in and awake, while you carry only your phone. Your sign-in can only happen on your own machine, so the laptop does every API call and the phone only talks to the laptop. Run `reseat serve` there. It runs the watcher and serves the web app: the dashboard, approve and today.
|
|
179
|
+
|
|
180
|
+
**Set up the phone.** Add a long random `serve_secret` to the rules file, then start it on the laptop with its Tailscale address (`tailscale ip -4` prints it):
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
reseat serve --host "$(tailscale ip -4)"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
It prints a one-time link. Open it once on each device. It sets a cookie and the address bar is left clean. If the cookie is lost, open `/login` on the same address and type the secret. A sign-in lasts 7 days. To sign every device out, stop and restart `reseat serve`: sign-ins live only in the running process. The page is plain HTTP, so open it over Tailscale, which encrypts the connection, not across open hotel Wi-Fi. Without `serve_secret`, `reseat serve` listens on 127.0.0.1 only and refuses any other address. It never listens on every interface: `--host 0.0.0.0` is refused. The page is access control for a hotel network, not a security product: it stops a neighbour on the same Wi-Fi from approving your swaps. Swap now and Dismiss take a plan id that expires after 10 minutes. No page or endpoint takes a session id.
|
|
187
|
+
|
|
188
|
+
**Keep the laptop awake with the lid closed.**
|
|
189
|
+
|
|
190
|
+
- macOS. `caffeinate -s reseat serve --host <Tailscale address>` keeps the Mac awake while it is plugged in. Closing the lid still puts most MacBooks to sleep unless an external display is attached. Either leave the lid open with the screen dimmed, or run `sudo pmset -a disablesleep 1` before you leave and `sudo pmset -a disablesleep 0` when you are back.
|
|
191
|
+
- Windows. Settings, System, Power and battery: when plugged in, sleep after Never. Control Panel, Power Options, Choose what closing the lid does: when plugged in, Do nothing.
|
|
192
|
+
|
|
193
|
+
**Reach it from the phone.** Install [Tailscale](https://tailscale.com) on the laptop and the phone and sign in to the same account on both. Start `reseat serve` with the laptop's Tailscale address and open the printed link on the phone. The phone then reaches the laptop from any venue, and the page is not offered on the hotel network.
|
|
194
|
+
|
|
195
|
+
**Push, if you want it.** Set `ntfy_topic` in the rules file to a long random name, install the ntfy app on the phone and subscribe to that topic. The laptop posts to `https://ntfy.sh/<topic>` when a seat is booked, a swap is proposed, done or rolled back, it is time to leave, the API has been unreachable for 10 minutes, it is back, or sign-in is needed. What leaves the laptop: the event type, session codes and titles, and those status lines. Never your token, an abstract, a session id or a plan id. Push is off unless the topic is set.
|
|
196
|
+
|
|
197
|
+
**When writes are switched off.** A 409 means the API has reservation writes turned off, and retrying will not help until they are back. re:Seat keeps watching and keeps every opening queued, but sends no reserve and runs no swap. It tries once after 15 minutes. With `probe_session` set to a session that takes no reservations, it checks every minute instead and resumes the moment writes open. Push says "Booking paused" and "Booking resumed".
|
|
198
|
+
|
|
199
|
+
**On site.** During the event days the laptop also checks the day's held and wanted sessions every 20 seconds, at most 40 of them, so a seat freed by a no-show is booked while you are in the walk-up line.
|
|
200
|
+
|
|
201
|
+
**When the hotel Wi-Fi drops.** The watcher keeps running. It retries with a growing wait, up to 5 minutes between tries, and goes back to its normal pace as soon as a sweep works. A sweep that fails halfway is not saved, so a seat that opened during the outage is still caught afterwards. Each outage goes in the journal. Your reserved seats stay reserved, because they live in your AWS schedule, not on the laptop. While the laptop is offline it cannot book anything and the phone cannot reach it, so use the official app until it is back. If the AWS API is down but the internet is up, push tells you "re:Seat offline since HH:MM" after 10 minutes and "re:Seat back" when it recovers.
|
|
202
|
+
|
|
203
|
+
**When sign-in expires.** If the token can no longer be refreshed, re:Seat stops booking, keeps watching for the moment it can read again, and tells you once: "sign in needed". Run `reseat login` on the laptop. Booking resumes on the next sweep.
|
|
204
|
+
|
|
205
|
+
### MCP server
|
|
206
|
+
|
|
207
|
+
`reseat mcp` runs a local MCP server over stdio, so an agent can read your targets and plan changes for you. Install the extra first: `pip install -e ".[mcp]"`. A client config looks like this, with the path to your `reseat` command:
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"mcpServers": {
|
|
212
|
+
"reseat": { "command": "/path/to/.venv/bin/reseat", "args": ["mcp"] }
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Tools: `list_targets`, `propose_changes`, `propose_swap`, `approve_changes`, `explain_drift`, `guard_sync`, `queue_or_go`. Every change is two steps. `propose_changes`, `propose_swap` or `guard_sync` returns a plan id and sends nothing. `propose_swap(held_code)` finds the open sitting of a held talk that your rules prefer to the one you hold, runs the swap's checks with fresh reads, and lists them. Approving it runs the same checked swap as `reseat swap`. `approve_changes` carries that plan out once, within 10 minutes, and reserves only what it named. A leave-now plan is refused if what you hold changed after it was shown. Calls run one at a time. No tool takes a list of session ids.
|
|
218
|
+
|
|
219
|
+
Know the limit: the server tells the agent to ask you before approving, but it cannot check that it did. The agent sees the plan id and could approve on its own. Session titles come from the catalog and reach the agent. Use a client that shows you each tool call before it runs.
|
|
220
|
+
|
|
221
|
+
### Good citizen rules
|
|
222
|
+
|
|
223
|
+
re:Seat only reserves what you asked for. It never holds two sittings of one talk. Watch lists are capped. It does not scrape or redistribute the catalog. Seats it frees during a swap go back to the pool at once.
|
|
224
|
+
|
|
225
|
+
### Development
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
pip install -e ".[dev]"
|
|
229
|
+
pytest
|
|
230
|
+
ruff check .
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
A fault storm runs the whole flow for an hour on the real catalog: 30 percent of reserves full, a 429 every minute, a 503, fifteen minutes of 409, quotas enforced. Tests run against an in-process fake of the API that produces every documented failure: `sessionFull`, `scheduleConflict` with `conflictsWith`, `alreadyScheduled`, 409 while writes are closed, 429 with `Retry-After`, and partial batch results. No network needed.
|
|
234
|
+
|
|
235
|
+
`tests/fixtures/catalog-2026-10-01.json` is a real catalog pull (no abstracts, no speaker names). `FakeEventsApi.from_fixture(path)` serves it, so tests see the real venue, room and type strings.
|
|
236
|
+
|
|
237
|
+
### Docs
|
|
238
|
+
|
|
239
|
+
- [How re:Seat works](docs/architecture.md): the design, each part marked built or planned, and how it is tested.
|
|
240
|
+
- [AWS Events API facts](docs/api-facts.md): the API behaviour re:Seat relies on, with sources.
|
|
241
|
+
- [Live proof](docs/proof/): checks run against the real API.
|
|
242
|
+
- [Demo scripts](demo/): recordable demos against the fake API. `reseat serve --demo` is the web app version.
|
|
243
|
+
- [Click-through demo](https://hsiddhu2.github.io/reseat/): the web app on demo data, rebuilt from `demo/site.py` on every push to main.
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
MIT
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Demos
|
|
2
|
+
|
|
3
|
+
Five short demos to record. Each runs the real re:Seat CLI and engine against an in-process fake of the AWS Events API, with real session codes and titles from the 1 October 2026 catalog. Nothing is sent to AWS and no sign-in is used. Each demo says so on screen.
|
|
4
|
+
|
|
5
|
+
These show behaviour against the fake. What has been checked against the real API is in [docs/proof](../docs/proof/).
|
|
6
|
+
|
|
7
|
+
Run from the repo root after `pip install -e .`:
|
|
8
|
+
|
|
9
|
+
| Demo | Command | Shows | Length |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| Book | `python demo/book.py` | The rules check, favorites sync, a dry run, then booking four targets. One sitting fills as it is booked, and re:Seat books that talk's next sitting in the same run. Another sitting already shows unavailable, so it is never sent and its next sitting is booked instead. Every write is read back. | about 40 s |
|
|
12
|
+
| Watch | `python demo/watch.py` | Four sweeps. A cancelled seat is booked the moment its band opens, then a repeat sitting AWS adds is booked as soon as it appears. | about 15 s |
|
|
13
|
+
| Safe swap | `python demo/swap.py` | A held session and a better one at the same time. First the better seat fills between the cancel and the reserve, and re:Seat restores the original. Then the swap goes through. | about 20 s |
|
|
14
|
+
| Approve on the phone | `python demo/phone.py --host <laptop Tailscale address>` | The approve view on the first morning of re:Invent: two held seats, a leave-now countdown and a proposed swap. Tap Approve on the phone. | as long as you like |
|
|
15
|
+
| Web app | `reseat serve --demo` | The dashboard, approve and today views on a scripted week. About 30 s in a seat opens and a swap waits for approval. About 60 s in a new sitting is booked. About 90 s in a held session changes room. Open `http://127.0.0.1:8491/`, or add `--host <laptop Tailscale address>` for the phone. | about 2 minutes |
|
|
16
|
+
|
|
17
|
+
## Recording
|
|
18
|
+
|
|
19
|
+
- **The web app, about 2 minutes.** Set the browser to 1440 by 900 and close other tabs. Start `reseat serve --demo`, then open `http://127.0.0.1:8491/` at once, since the script starts with the command. Shot list:
|
|
20
|
+
1. 0:00 to 0:20. The week: held sessions, the yellow leave-now strips, the status line saying it watches in this process.
|
|
21
|
+
2. About 0:30. The swap card appears. Read the four checks and the sequence line under the buttons.
|
|
22
|
+
3. About 1:00. Last changes shows the new sitting booked and read back. The counts panel goes to 1 seat booked.
|
|
23
|
+
4. About 1:30. CMP303 on Wednesday says it moved room.
|
|
24
|
+
5. Press Swap now. The result line says verified and what is held now. Scroll to the journal: proposed, checked, cancel, reserve, verified.
|
|
25
|
+
6. Narrow the window to phone width, or open `/today`: the leave countdown, the next sessions, the personal dinner, the wanted session and its clash line.
|
|
26
|
+
|
|
27
|
+
- **Terminal.** Use [asciinema](https://asciinema.org) (`asciinema rec book.cast -c "python demo/book.py"`), or a screen recording of a terminal at least 120 columns wide so the tables do not wrap.
|
|
28
|
+
- **Phone page.** On the laptop, run `demo/phone.py` with the laptop's Tailscale address. It refuses `0.0.0.0`, so the demo never listens on hotel or home Wi-Fi. It uses port 8491, so it does not sign the phone out of a real `reseat serve` on 8490. On the phone, open the printed link over Tailscale before you start recording: a used link is harmless, an unused one works for an hour. Then start the phone's screen recording and tap Approve. The swap result shows on the page within a second.
|
|
29
|
+
- **Before publishing a recording,** check it against the redaction rules in [docs/proof/README.md](../docs/proof/README.md). In particular, blur the one-time link and crop the phone's address bar and status bar. The demo data is not personal, but the link, host name and Tailscale address are.
|