ESPN Fantasy
Get a durable, logged-in ESPN session, then drive the team through the ESPN Fantasy API instead of clicking around the site.
Golden rules (learned the hard way)
- Use ONE real HEADED browser. Do not run headless — ESPN's AWS WAF + invisible
reCAPTCHA block
chrome-headless-shell("Max challenge attempts exceeded"). If the box has no usable display, start a virtual headed display with TigerVNC:scripts/start-display.sh :1, then run the browser withDISPLAY=:1 HEADED=1. - One login attempt at a time. Never run two login processes, and never rapid-retry. Concurrent/fast attempts trip ESPN's 2FA code-send rate limit ("unable to send you any more codes right now"). The script backs off ~12 min itself.
- Keep logins persistent. The browser profile (cookies + localStorage) lives on disk at
state/espn-profile/and is reused every run — you log in once, not every time. Also saveespn_s2+SWIDto.secrets/espn-cookies.txtfor API use. - If the profile gets "poisoned" (repeated failures → the challenge error), throw it away
and start a fresh profile (
ESPN_PROFILE=state/espn-profile-fresh); a clean profile loads the login form fine. - Never paste keys/cookies/codes into chat. Creds live in
.secrets/(600, root-only). - Kill by PID, never by pattern.
pkill -f login-interactivematches the calling shell and SIGTERMs your own command. Usekill <pid>(orpgrep -x node+/proc/<pid>/cmdline).
Workflow
1. Start the headed display (once)
cd skills/espn-fantasy/scripts
./start-display.sh :1 # starts Xvnc on :1, no-auth, localhost-only
2. Run the login (single instance)
DISPLAY=:1 XAUTHORITY=$HOME/.Xauthority HEADED=1 FULLCHROME=1 node login-interactive.mjs &
- Creds from
.secrets/espn-login.txt(ESPN_USER=,ESPN_PASS=). - Headed browser on the VNC display; persistent profile
state/espn-profile/. - Logs to
state/espn-login.log; live screenshotstate/espn-live.png. - On success writes
.secrets/espn-cookies.txtand logsSUCCESS.
3. Hand off the 2FA code
The account has email 2FA. When the log shows 2FA_PROMPT active, ask the user for the code
from their email and drop it on disk:
printf '123456' > state/espn-2fa-code.txt
The running script picks it up within ~3s (it waits up to 45 min). Code = single-use, ~10 min TTL.
4. Use the API
Load .secrets/espn-cookies.txt (espn_s2, SWID) and call the fantasy API. See
references/espn-fantasy-api.md.
Quick env reference
| Var | Effect |
|---|---|
HEADED=1 |
run the real headed browser (required) |
DISPLAY=:1 |
the VNC virtual display |
FULLCHROME=1 |
use full Chromium build (needed for headless; harmless headed) |
ESPN_PROFILE=<dir> |
override profile dir (use for a fresh, un-poisoned profile) |
Monitoring (local, no-LLM)
Never poll the ESPN API from the model (token burn + bot risk). Instead run scripts/monitor.mjs
on a systemd user timer. It diffs the league feed and appends only NEW events to
state/espn-alerts.jsonl; a quiet run produces nothing.
Watches: incoming/pending trade proposals, newly executed transactions (waivers, FA adds/drops, completed trades), and injury-status changes on the user's roster.
Install (units already on this box):
~/.config/systemd/user/espn-monitor.service # Type=oneshot -> node monitor.mjs
~/.config/systemd/user/espn-monitor.timer # OnUnitActiveSec=12min
systemctl --user enable --now espn-monitor.timer
Surface alerts cheaply from a heartbeat: if state/espn-alerts.jsonl is non-empty, relay it to
the user then truncate; if empty, stay silent.
Resources
scripts/start-display.sh— start the virtual headed display (Xvnc).scripts/lib.mjs— browser launcher: persistent profile, headed/full-Chromium modes, stealth init.scripts/login-interactive.mjs— full flow: iframe fill, 2FA wait, rate-limit backoff, cookie capture.scripts/tx.mjs— WRITE helper:setLineup,addDrop,quickLineup(uses the verified bare-transaction format).scripts/monitor.mjs— no-LLM league watcher (diff -> alerts file).references/login-flow.md— exact selectors, failure modes, timings.references/strategy.md— team-management strategy (weekly rhythm, start/sit, waivers, trades/trade-radar, monitoring cron design), open questions, current status, rollout phases.references/espn-fantasy-api.md— fantasy API endpoints for reading/managing a team.
