Commish
A discord friendly fantasy football commissioner tool.
Commands this plugin adds
About this plugin
Commish
Your fantasy football league, surfaced in Discord — weekly matchups, power rankings, injury alerts, waiver and start/sit reminders. Full Sleeper support, plus Fleaflicker (v2.0) and Fantrax (v2.1).
A plugin for YourBot.gg — runs sandboxed in the platform and reacts to Discord events on installed servers.
What it does
Links one or more fantasy leagues per Discord server — Sleeper (full support), Fleaflicker (core support, v2.0+) and Fantrax (standings/schedule tier, v2.1+) and exposes 32 manual slash commands — pre-week (/commish-matchups, /commish-week), in-week (/commish-scores, /commish-roster, /commish-waiver-claims), retrospective (/commish-rankings, /commish-standings, /commish-streak-watch, /commish-luck, /commish-all-play, /commish-record-book, /commish-points-per-position, /commish-h2h, /commish-budget, /commish-injuries, /commish-transactions, /commish-trade-history, /commish-trends, /commish-nfl), season-context (/commish-draft, /commish-playoffs) and admin/config (/commish-setup, /commish-leagues, /commish-switch, /commish-unlink, /commish-channel, /commish-schedule, /commish-doctor, /commish-status, /commish-help) — plus a set of UTC-scheduled posts (waiver reminders, weekly previews, start/sit prompts, end-of-week recap, trade-deadline alert, playoff-clinching alerts, recurring injury scans, hourly transaction digest), with lazy-fallback dispatch for pool-mode resilience. League data comes from each platform's public, no-auth API: Sleeper at api.sleeper.app, Fleaflicker at www.fleaflicker.com/api, Fantrax at www.fantrax.com/fxea; live NFL scores come from ESPN's public site API at site.api.espn.com (no league needed). Internally, provider adapters map foreign payloads into Sleeper shapes, so the same command surface serves every platform; league refs are composite (fleaflicker:<id>, fantrax:<id>) for non-Sleeper platforms. A per-platform feature matrix (PLATFORM_FEATURES) gates what each platform's API can actually power — unsupported commands reply with a clear refusal naming the platform instead of failing.
| Feature | Sleeper | Fleaflicker | Fantrax |
|---|---|---|---|
| Standings, rankings, week snapshot, rosters | ✅ | ✅ | ✅ |
| Weekly matchups | ✅ | ✅ | ✅ (schedule only — fxea has no scores) |
| Live scores, luck, all-play, record book, h2h, recap, streaks | ✅ | ✅ | ❌ |
| Transactions, waivers, trade history, FAAB | ✅ | ❌ | ❌ |
| Draft, playoffs, injuries, skill rankings, points-per-position | ✅ | ❌ | ❌ |
Channels for each post type are configured per-server via /commish-channel. If a given post type doesn't have a channel set, it falls back to whichever channel was set for general; if that's also unset, the cron logs and skips silently.
Sleeper integration
Sleeper is the primary platform; the plugin talks to api.sleeper.app (public endpoints, no auth, no API key) — see Capabilities for the full proxy-domain list across all platforms. The Sleeper endpoints used are:
| Endpoint | Used for |
|---|---|
GET /v1/state/nfl |
Current week + season type — gates regular-season-only cron tasks. |
GET /v1/league/{id} |
League validation in /commish-setup and league metadata. |
GET /v1/league/{id}/users |
Team owner / display name lookup. |
GET /v1/league/{id}/rosters |
Roster contents, win/loss/points-for stats, rostered player IDs. |
GET /v1/league/{id}/matchups/{week} |
Weekly matchup pairings + points. |
GET /v1/players/nfl |
Player names, positions, teams and injury_status. See known limitation below. |
Multi-league (v0.6+): a single Discord server can link any number of Sleeper leagues. Channel mappings are per-league — each league has its own general / waivers / news / previews / rankings channel set, so two leagues can post to two different channel groups in the same server. One league is "active" at a time; commands like /commish-week, /commish-rankings, etc. operate on the active league. Use /commish-switch league:<id-or-name> to change.
Embedded player catalog — workaround for the 1 MB proxy cap
Sleeper's full NFL player catalog is roughly 5 MB. The YourBot.gg outbound-HTTP proxy caps response bodies at 1 MB, so it can't be fetched at runtime. v0.3 sidesteps this by bundling a trimmed snapshot of the catalog inside __main__.py — fantasy-relevant positions only, five fields per player (full_name, first_name, last_name, team, position, injury_status), zlib-compressed + base64. Refresh it before every release with make refresh-players. The catalog is the only Sleeper endpoint we can't proxy; everything else (rosters, users, matchups, transactions, state) is hit live.
Trade-offs:
/commish-injuriesshows the snapshot'sinjury_statusand includes a footer with the snapshot date. Accuracy is good for ~a week (releases cadence); for live injury news, see Sleeper's own notifications.- The injury-scan cron still fires every 6 hours but now only posts when the embedded data has changed — effectively a delta-on-release digest.
- Players added between releases (rookies, new signings) show up as
Player <id>in transaction posts until the next refresh.
This is the workaround until the YourBot.gg platform ships a server-side player cache, at which point the embedded snapshot can be removed entirely.
Capabilities
This plugin requests the following capabilities (all Safe tier — no Risky or Dangerous caps):
| Capability | Tier | Why |
|---|---|---|
discord:send_message |
Safe | Post matchups, rankings, injury updates and reminders from cron and slash commands. |
interaction:respond |
Safe | Reply to all 32 slash commands (auto-added because slash_commands is non-empty, declared explicitly for review clarity). |
proxy:http |
Safe | Fetch league data from api.sleeper.app, www.fleaflicker.com and www.fantrax.com, plus live NFL scores from ESPN's public site.api.espn.com. |
storage:kv |
Safe | Per-server league config, channel mappings, and per-player injury-status state for cron dedup. |
proxy_domains_requested is exactly ["api.sleeper.app", "www.fleaflicker.com", "www.fantrax.com", "site.api.espn.com"]. Nothing else is reachable.
If any future capability would push the plugin into Risky or Dangerous tier, that change will be called out in CHANGELOG.md and bump the major version.
Slash commands
Admin-only commands (/commish-setup, /commish-channel, /commish-schedule) should be restricted via Discord's per-command permissions in Server Settings → Integrations. The YourBot.gg manifest schema doesn't have a server-side admin flag, so gating is enforced at the Discord layer, not in the plugin code.
Setup & configuration (admin)
| Command | Description |
|---|---|
/commish-setup sleeper_league_id:<id> |
Validates the Sleeper league ID against the API and links it to this server. As of v0.6 this adds to the linked-league list — running it more than once with different IDs gives you multiple linked leagues, with the most-recently-added one becoming active. |
/commish-leagues |
List every league linked to this server, with ⭐ marking the active one. |
/commish-switch league:<id-or-name> |
Change which linked league is active (admin). Accepts either a Sleeper league ID or a unique substring of the league name. |
/commish-unlink league:<id-or-name> |
Remove a linked league (admin). Per-league channel mappings for the unlinked league are deleted. |
/commish-channel type:<...> [league:<id-or-name>] |
Sets the CURRENT channel as the destination for one of general, waivers, news, previews, rankings. As of v0.6 channel mappings are per-league — pass league: to target a specific league, otherwise the active league is configured. |
/commish-schedule |
Shows when each cron post fires next for the active league, and which channel each will land in. |
/commish-doctor |
Diagnostic snapshot — linked leagues, channel coverage, Sleeper API connectivity, KV read/write health, and per-cron last-fired timestamps. Ephemeral. |
Discovery & info
| Command | Description |
|---|---|
/commish-help |
Lists every command and shows the current setup status. The "I just installed this, now what" command. |
/commish-status |
Linked league + channel mappings (no command listing — that's /commish-help). |
League content
| Command | Description |
|---|---|
/commish-week |
One-shot snapshot for the current week — header + matchups + top-3 power rankings, in a single message. Best for daily check-ins. Carries quick-action buttons (📊 Standings, 📈 Rankings, 🏈 Live scores) that post the full view in one click; the Live-scores button only appears for platforms that expose live scoring (v2.2). |
/commish-matchups |
Posts this week's matchups embed. |
/commish-scores |
Live in-progress scoring — same week as /commish-matchups, sorted by closest matchup first. Shows leader, spread, and an approximate yet-to-play starter count per team. Footer carries the UTC snapshot timestamp so you can tell whether the data is fresh. Carries a 🔄 Refresh button to re-pull live scores in one tap (v2.4). Regular-season only. |
/commish-rankings [mode:all|skill] |
Power rankings. Default all ranks by roster.settings.fpts. skill recomputes from per-player matchup data, excluding K and DEF — needs at least one completed regular-season week. |
/commish-standings |
Full standings table — W-L-T record, PF, PA, streak, and games-back from the leader. Record-first sort (PF as tiebreaker), the complement to /commish-rankings's points-first sort. |
/commish-streak-watch |
Current active streaks per team, sorted hottest-to-coldest (W3 → W1 → none → L1 → L3). Reads Sleeper's native roster.metadata.streak field — same source /commish-standings uses for its streak column. |
/commish-roster [team:<id-or-name>] |
Show one team's starters (by lineup slot), bench, IR/reserve, and taxi squad. team: accepts a team-name substring, owner display name, or numeric roster ID. Omitting it shows a clickable team-picker (one button per team, v1.13). |
/commish-luck |
Luck rating — each team's actual W-L vs expected wins from all-play (you beat everyone who scored less than you that week). Positive luck = winning more than scores alone would predict. Needs at least one completed regular-season week. |
/commish-all-play |
All-play standings — every team's record as if it played everyone every week (beat all who scored less, lose to all who scored more; equal scores are half-wins), ranked by all-play win%. Strips schedule luck out of the standings, so a team can be 5-8 head-to-head yet near the top. Shares the tested all-play math with /commish-luck. Needs at least one completed regular-season week (v2.5). |
/commish-record-book |
Season records — highest single-week score, biggest blowout, closest game, longest win streak, longest losing streak, toughest schedule (most points-against). Needs at least one completed regular-season week. |
/commish-points-per-position |
Per-team season scoring broken down by lineup slot (QB / RB / WR / TE / FLEX / K / DEF — whatever the league's roster_positions declares). Reveals positional strengths. Same-name slots (two RB slots) lump into one column. Skips BYE weeks. |
/commish-h2h team1:<id-or-name> team2:<id-or-name> |
Head-to-head record between two teams across every completed regular-season week they shared a matchup_id. Shows per-game scores, total PF on each side, and average margin. Both args required — reuses the _resolve_team_arg resolver per-arg so ambiguous/unknown matches surface per-team error messages. |
/commish-budget |
FAAB budget remaining per team, sorted most-remaining first. FAAB-only leagues (priority-based waiver leagues see a "doesn't use FAAB" message). Reads league.settings.waiver_budget and roster.settings.waiver_budget_used. |
/commish-injuries |
Lists injured / questionable / doubtful / out players across the league. Data is from an embedded snapshot — refresh date shown in the footer. |
/commish-transactions |
This week's completed transactions (trades, waivers, free agents) in a single embed. Manual catch-up companion to the hourly digest cron. |
/commish-waiver-claims |
This week's pending waiver claims (pre-Tuesday processing), grouped by team and sorted within each team by FAAB bid descending. Companion to /commish-transactions — useful for "who's bidding on whom?" before the waiver run. |
/commish-trade-history [team:<id-or-name>] |
Every completed trade this season, newest week first. Optional team: arg narrows to trades involving that team only. Reuses the same team: resolver as /commish-roster. |
/commish-trends |
Most-added and most-dropped NFL players across all Sleeper leagues, past 24h. Doesn't require a linked league. |
/commish-nfl |
Live NFL game scoreboard from ESPN's public API, bucketed In progress → Upcoming → Final (live games first). Platform-agnostic — doesn't require a linked league (v2.3). |
/commish-draft |
League draft summary — date/time, format (teams × rounds × snake/linear), current status (pre-draft / drafting / complete), and the most recent picks once drafting starts. |
/commish-playoffs |
Playoff bracket — championship side only, with team names resolved at each round and winners marked once games complete. |
If /commish-setup hasn't been run, every command replies ephemerally with a "run /commish-setup first" hint and posts nothing.
Scheduled posts (cron — UTC)
| When | Channel type | What |
|---|---|---|
| Tue 02:00 UTC | waivers |
Waiver-wire reminder. |
| Tue 10:00 UTC | general |
Trade-deadline alert — fires only on the league's configured trade_deadline week. |
| Tue 14:00 UTC | rankings |
Weekly power rankings (all positions; use /commish-rankings mode:skill for the K/DEF-excluded view). |
| Tue 16:00 UTC | general |
End-of-week recap — final scores + winners + biggest blowout + closest game for the week that just ended. |
| Tue 17:00 UTC | general |
Playoff clinching update — fires once per team when they're mathematically locked into the playoffs (or eliminated). KV-deduped so each transition is announced only once. |
| Thu 14:00 UTC | previews |
This week's matchup preview. |
| Sat 17:00 UTC | general |
Start/sit prompt. |
| Every 6 hours | news |
Injury scan — posts only when at least one rostered player's injury_status has changed since the last scan (KV-backed dedup keyed by player ID). Since the player catalog is embedded, changes only show up after make refresh-players ships in a release. |
| Hourly (top of hour) | news |
Transaction digest — posts each new completed trade / waiver / free-agent move once. Skipped outside the regular season. |
Channel fallback: if a given post type's channel isn't set, the plugin uses general. If general is also unset, the cron logs and skips. Pre-season and post-season runs (state.nfl returns week=0 or season_type != "regular") skip every content cron — matchup preview, start/sit, waiver reminder, power rankings, recap, deadline/clinching alerts and the transaction digest (as of v1.12 the waiver reminder and power rankings are season-gated like the rest); only the injury scan still runs year-round, and it only posts on actual status changes.
/commish-schedule is the easiest way to verify that each cron will fire where you expect.
Pool mode caveat — important
Per the SDK reference (the platform sandbox docs): @plugin.cron and @plugin.schedule do not fire in pool mode. Low-traffic plugins may be assigned to pooled workers, which don't carry per-install state, and the scheduler skips them.
As of v1.2, Commish ships a lazy-cron fallback for pool-mode resilience without depending on the platform's (undocumented-in-SDK) server-side cron schema. Each @plugin.cron handler is wrapped to record its successful fire in state:cron_last_fired:<key>; on every (worker, server) bootup, on_ready (which fires in pool mode — a guarantee that landed in legacy SDK 0.5.2 and carries through yourbot-sdk 0.6.x) checks each cron's last-fired timestamp against its schedule and re-fires anything overdue. First-run handling: an empty timestamp seeds without firing, so a freshly-installed server doesn't get spammed with nine catch-up messages on first event. As of v1.12 the sweep runs on a daemon thread (so it can't delay the interaction that woke the worker past Discord's 3-second ack window) and takes an atomic per-slot KV claim before firing (so two pool workers can't double-post the same overdue cron).
The convergence on KV state means: in single-tenant mode @plugin.cron fires the live cron and the lazy check observes "already fired", skipping; in pool mode the live cron is silenced but the lazy check finds the gap and bridges it on next inbound event. Either path produces the same KV state of record.
/commish-doctor exposes the last-fired timestamps so you can see at a glance which crons are tracking and which are silent.
In the meantime, the manual slash commands (/commish-matchups, /commish-rankings, /commish-injuries, /commish-week, etc.) work identically whether or not the cron fires.
Lifecycle hooks DO work in pool mode under yourbot-sdk 0.6.x (this plugin pins yourbot-sdk>=0.6.1,<0.7.0; the SDK was renamed from mmo-maid-sdk at 0.6.0 and the old import keeps working only via a deprecated shim). Specifically, @plugin.on_ready fires once per (worker, server) on the first event for each tenant, synchronously before the event handler runs. That guarantee is what makes the v0.6 multi-league migration safe: when a pool-mode worker receives its first event for a server that's still on the legacy single-league schema, on_ready runs _migrate_legacy_league_keys() to completion before the user-facing handler sees KV. The guarantee first landed in legacy SDK 0.5.2 (pool-mode on_ready was effectively non-functional in 0.5.0/0.5.1) and is preserved through the 0.6.x rename releases.
KV schema
Colon-delimited keys per the SDK style guide. KV is automatically namespaced by plugin_id and server_id at the runtime layer, so these keys do not include either prefix.
| Key | Purpose |
|---|---|
cfg:leagues |
List of every linked Sleeper league: [{"id": "...", "name": "..."}]. |
cfg:active_league_id |
The league ID that "active-league" commands target by default. |
cfg:channel:<league_id>:<type> |
Destination channel ID for a post type within a specific league. Per-league as of v0.6. |
cfg:migrated_to_multi_league |
Set to "1" after the v0.6 schema migration runs. Idempotent guard. |
cfg:league_id / cfg:league_name / cfg:channel:<type> |
Legacy (pre-v0.6) single-league keys. Read once during migration, then ignored. Left in place after migration so a v0.5 downgrade still works. |
state:last_news_scan |
ISO timestamp of the most recent injury-scan cron run. |
state:player_status:<player_id> |
Last-seen injury status for a rostered player, used by the injury-scan cron to dedup. Deleted when a player returns to "Active"; written with a 90-day TTL so players dropped while injured don't hold keys forever. |
cache:rostered_players:<league_id> |
List of player IDs currently rostered in a league. Refreshed by the injury cron at most once per day. League-scoped as of v1.12 (the old unscoped key is orphaned, not read). |
cache:rostered_players:<league_id>:ts |
ISO timestamp of the last rostered-players refresh for that league. |
cache:matchups:<league_id>:<week> |
Trimmed matchup data for a completed week (roster_id, matchup_id, points, starters, starters_points). Immutable once a week ends, so season-wide commands and dashboards read KV instead of re-walking Sleeper. The most recent completed week carries a 2-day TTL to pick up stat corrections. |
cache:trades:<league_id>:<week> |
Trimmed completed trades for a completed week — same caching rules as cache:matchups. |
state:transactions:seen |
Ordered list (oldest→newest) of transaction IDs the hourly digest cron has already posted. Capped at 500 entries, trimmed oldest-first. |
state:playoff_status:<league_id>:<roster_id> |
Per-team playoff status (clinched / eliminated / unset). League-scoped as of v1.12; the pre-v1.12 unscoped key is read once as a fallback, then migrated. |
state:cron_last_fired:<cron_key> |
ISO UTC timestamp of each cron's most recent successful fire. Written by the cron's @_register_lazy_cron wrapper; read by the lazy fallback dispatcher in on_ready (pool-mode resilience). Surfaced in /commish-doctor. |
state:cron_claim:<cron_key>:<due-slot> |
Atomic fire claim (kv.increment) preventing two pool workers from double-posting the same overdue cron. Self-expires after 7 days. |
state:doctor_probe |
Transient KV health-check value written and immediately deleted by /commish-doctor; only persists if the delete fails. |
The full Sleeper player catalog is never cached in KV — it's ~5 MB and would blow the 64 KB per-value limit anyway. A trimmed snapshot is instead bundled in __main__.py (see "Embedded player catalog" above). Per-player status is stored individually (each value is a single short string).
Configuration walkthrough
- Install the plugin from the YourBot.gg marketplace; grant the four capabilities listed above.
- Link your league: in any channel, run
/commish-setup sleeper_league_id:<your-id>(the long number in the Sleeper URL, e.g.https://sleeper.com/leagues/123456789012345678/league) — or/commish-setup-fleaflicker league_id:<your-id>for a Fleaflicker league (the number infleaflicker.com/nfl/leagues/<id>), or/commish-setup-fantrax league_id:<your-id>for a Fantrax league (the letters-and-digits code infantrax.com/fantasy/league/<id>/...). - Set channels. Easiest: click "Post league updates in this channel" on the setup confirmation — that maps
general, which every other post type falls back to. For finer routing, run/commish-channel type:<type>in each channel you want a specific post type to land in. - Verify setup: run
/commish-help(or/commish-status) to confirm the league is linked and channels are mapped, then/commish-scheduleto see when the next cron post will fire. - Try it:
/commish-weekfor the daily snapshot — then tap its Standings / Rankings / Live scores buttons to drill in without retyping./commish-standingsand/commish-rankingsgive the leaderboard views (record-first vs PF-first),/commish-roster team:<name>a specific lineup,/commish-scoreslive in-progress points during a slate./commish-injuries,/commish-transactions,/commish-draft,/commish-playoffs,/commish-trendsand/commish-nfl(live NFL scoreboard — no league needed) round out the catalog.
The cron jobs start firing automatically at their next UTC trigger. No further setup needed.
Roadmap
v1.0 shipped the Sleeper-only, full-featured milestone. As of v2.1 the project is multi-platform (Fleaflicker and Fantrax shipped): a provider abstraction so the same command surface can serve leagues hosted on other fantasy platforms, starting with the ones whose APIs work inside the platform sandbox (public JSON, no OAuth — Fleaflicker first; ESPN read-only next; Yahoo stays blocked until the platform offers an OAuth broker, because Yahoo's token refresh requires a client secret the sandbox can't hold). The catalog cap forced a build-time snapshot rather than a runtime fetch (see "Embedded player catalog" above).
Shipped
v0.3
- ✅ Embedded player catalog — workaround for the 1 MB proxy cap. Verified there's no smaller Sleeper or third-party feed that joins to Sleeper rosters, so we bundle a trimmed snapshot in
__main__.py. Refresh viamake refresh-playersbefore each release. - ✅ Transaction-feed alerts —
cron_transaction_digest(hourly) posts new trades, waivers and free-agent moves into thenewschannel as they happen./commish-transactionsis the manual catch-up companion.
v0.4
- ✅ DST/kicker filtering for power rankings —
/commish-rankings mode:skillrecomputes points-for from per-player matchup data, excluding K and DEF. Uses the embedded catalog's position metadata. - ✅ End-of-week recap cron — Tue 16:00 UTC during regular season. Final scores + winners + biggest blowout + closest game.
- ✅ Trade-deadline alert cron — Tue 10:00 UTC, fires only on the week matching the league's configured
trade_deadline. - ✅
/commish-trends— most-added / most-dropped players across all Sleeper leagues, past 24h.
v0.5
- ✅
/commish-draft— draft date, format, status, and the last N picks once drafting starts. Picks the most recent regular-season draft when a league has multiple (mock + real). - ✅
/commish-playoffs— winners-bracket view rendered as Quarterfinals / Semifinals / Championship, with team names resolved at each round andwinner of m{X}placeholders for unresolved slots. Completed matchups show ✅ + strike-through loser. - ✅ Playoff clinching cron — Tue 17:00 UTC weekly during regular season. Announces each team's transition to "clinched" or "eliminated" exactly once via KV dedup. Uses purely-mathematical win-count analysis (PF tiebreakers can still flip an edge case but the message is "guaranteed top-N by wins").
v0.6
- ✅ Multi-league per server — KV schema refactored from single
cfg:league_idtocfg:leagues+cfg:active_league_id, with per-league channel mappings (cfg:channel:<league_id>:<type>). Legacy single-league installs auto-migrate on first read after deploy. New slash commands:/commish-leagues,/commish-switch league:<...>,/commish-unlink league:<...>./commish-setupnow adds rather than replaces. - ✅ Manifest-mode dashboard (
dashboard_manifest.json). Two pages: Overview (linked-leagues stat card, current NFL week, standings leader for active league, recent-transactions table) and Settings (linked-leagues markdown summary + 5-channel form for the active league). Seven@plugin.on_dashboard(...)handlers.
v0.7
- ✅
/commish-standings— full standings table (W-L-T record, PF, PA, streak, games-back). Sorts by record first with PF as tiebreaker, the complement to/commish-rankings's points-first sort. Readsroster.settings.fpts_againstandroster.metadata.streak(both Sleeper-provided, both untouched by existing commands). Code-block rendered for monospace column alignment.
v0.8
- ✅
/commish-roster [team:<id-or-name>]— per-team lineup view. Shows starters byleague.roster_positionsslot (QB / RB / WR / TE / FLEX / K / DEF / SUPER_FLEX / IDP — whatever the league declares), bench, IR/reserve (with injury status), and taxi squad. Team resolver accepts a team-name substring, owner display name, or numeric roster ID; omitting the arg returns an ephemeral "pick a team" list, ambiguous matches list the candidates, and bad input gracefully lists all teams as fallback.
v0.9
- ✅
/commish-scores— live in-progress matchup scoring. Same Sleeper endpoints as/commish-matchupsbut presents the pairings sorted closest-spread first so daily check-ins see the dramatic games at the top, not the matchup-ID-ordered list. Each pair shows leader, spread, and a yet-to-play starter count (heuristic: starters with exactly 0.00 points so far). Footer carries the UTC snapshot timestamp.
v0.10
- ✅ Regression / edge-case pass. Two
/commish-rosterbugs fixed (partial lineups dropping slots silently; IR section emitting— —noise when no injury status is known) and seven new regression tests pin behaviors that should stay stable as the plugin evolves — standings + rankings during post-season, BYE rendering, all-zero scores edge, mid-season setup, and the two roster fixes above.
v1.0
- ✅ Documentation pass. README updated to reflect the v0.7→v0.10 push: command count corrected (9 → 19) everywhere it was stale, "What it does" rewritten to call out the full slash-command surface, "Try it" walkthrough expanded with the new commands and when to reach for each.
- ✅ Feature surface frozen. No new slash commands or schema changes planned for v1.0+ on the Sleeper-only path. (Re-opened at v1.1 — see below.)
v1.1 (post-v1.0 feature expansion)
- ✅
/commish-luck— luck rating. Per-team, computes actual wins vs expected wins from all-play (you "win" against every team that scored less than you each week). Sorted luckiest-first; positive luck = winning more than scores alone predict, negative luck = losing despite scoring well. Uses the same multi-week matchup loop as the skill-rankings code path. - ✅
/commish-record-book— single-season aggregates: highest single-week score, biggest blowout, closest game, longest win streak, longest losing streak, toughest schedule (most PA). Shared_completed_weeks_matchupshelper with/commish-luckso both commands pay the same N proxy calls once each. - ✅
/commish-trade-history [team:<id-or-name>]— every completed trade across the season, newest week first. Optional team filter reuses the/commish-rosterresolver. Filters out waivers and free-agent moves (those live in/commish-transactions).
v1.2 (operational)
- ✅ Lazy-cron pool-mode fallback. The SDK's "server-side cron in manifest" mitigation isn't documented at the schema level, so Commish takes the documented alternative: each
@plugin.cronrecords its successful fire instate:cron_last_fired:<key>, andon_ready(guaranteed once per (worker, server) under SDK v0.5.2+) re-fires anything overdue. First-run seeding prevents spam on fresh installs; corrupt timestamps re-seed defensively. A drift-guard test fails CI if a new@plugin.cronis added without the@_register_lazy_crondecorator. - ✅
/commish-doctor— diagnostic snapshot: leagues + active, channel coverage, Sleeper API reachability + current week, KV read/write probe, and per-cron last-fired relative times ("2h ago", "3d ago"). Ephemeral admin tool, useful for ops debugging and as a contract for the new last-fired KV slots.
v1.3 (dashboard polish)
- ✅ Dashboard expanded from 2 pages to 4. New "Live week" page (top score stat, closest spread stat, this-week matchups table sorted closest-first) and "Season summary" page (total trades stat, top-3 luckiest teams list, season records list). Six new
@plugin.on_dashboardhandlers, all reusing the same_build_*_embed/_completed_weeks_matchupshelpers that power the slash commands so no business logic is duplicated.
v1.4 (massive regression sweep)
- ✅ Two real fixes surfaced during the audit.
/commish-luckand/commish-record-bookwere both including BYE-week roster entries (matchup_id=None) in their math — phantom all-play comparisons in luck, and a phantom "high score" record without a real game behind it. Both fixed; only affected odd-team leagues where rotating BYEs exist. - ✅ 21 new regression tests across five themes — Sleeper-outage resilience for every public command, multi-league isolation, season-boundary behavior, per-cron lazy-fallback smoke, and BYE-week math — plus a single end-to-end install→use→unlink journey that exercises the full lifecycle in one MockContext.
- ✅ Latent timing bug surfaced. The v1.2 lazy-cron skip test was flaky on the top-of-the-hour boundary; refactored
_lazy_cron_check_and_fireto accept an optionalnowparameter for deterministic testing.
v1.5 (FAAB visibility)
- ✅
/commish-budget— FAAB remaining per team, sorted most-remaining first with wins as tiebreak. FAAB-only command — leagues using priority-based waivers (no FAAB) get an explanatory message rather than a meaningless$0/$0table. Readsleague.settings.waiver_budget(total) androster.settings.waiver_budget_used(spent).
v1.6 (head-to-head)
- ✅
/commish-h2h team1 team2— head-to-head record between two specific teams. Iterates_completed_weeks_matchups(the v1.1 helper) and counts only weeks where both teams shared amatchup_id. Renders per-week scores, total PF on each side, and average margin. Both args required; team resolution reuses_resolve_team_argper-arg so an ambiguous or unknownteam1surfaces as a**team1:**prefixed error rather than confusing the user about which arg was wrong.
v1.7 (pre-waiver visibility)
- ✅
/commish-waiver-claims— this week's pending waiver claims (Sleeper marks themstatus="pending",type="waiver"pre-processing; after Tuesday's run they flip tocompleteorfailed). Grouped by team (most active first), sorted within each team by FAAB bid descending — mirrors Sleeper's own resolution order. Renders+ AddPlayerand optional/ − DropPlayer. Companion to/commish-transactionswhich only surfaces post-run results.
v1.8 (observability)
- ✅
ctx.metricsinstrumentation across the three highest-signal paths.sleeper.requestcounter tagged withpath(normalized —league/{id}/rosters, not the literal league_id) andoutcome(success/failure/rate_limit/retry) gives a per-endpoint health view in the metrics dashboard.cron.fired(from the wrapper) andcron.lazy_fired(from the lazy dispatcher) together partition the cron fire path — the ratio surfaces whether the plugin is running pooled (lazy carries the schedule) or single-tenant (scheduler does).command.invoked(from the new_slash_command(name)wrapper around@plugin.on_slash_command) is tagged with the command name so per-command usage rolls up cleanly. All three write paths are wrapped in try/except so a metrics outage can't break a Sleeper call, a cron handler, or a slash command. Drift guards in the test suite fail loudly if a new cron skips@_register_lazy_cronor a new command skips@_slash_command.
v1.9 (positional breakdown)
- ✅
/commish-points-per-position— per-team scoring breakdown by lineup slot across all completed regular-season weeks. Readsleague.roster_positionsto label each starter-index, sums points, lumps same-name slots (two RB slots → one RB column), skips BN/IR/RSV/TAXI and BYE weeks. Code-block rendered to align columns; truncates to first 8 slot types if a league has more (rare).
v1.10 (streak watch)
- ✅
/commish-streak-watch— current active streaks per team, sorted hottest-to-coldest. Reads Sleeper'sroster.metadata.streakvia the v0.7_format_streakhelper — same data source/commish-standingsuses for its streak column. New_streak_sort_value()mapsW3 → +3,L1 → −1,— → 0for ranking. Cheaper than the multi-week views (no per-week loop).
v1.11 (observability dashboard)
- ✅ Dashboard expanded from 4 pages to 5. New "Observability" page wires the v1.8
ctx.metricscounters (sleeper.request,cron.fired,cron.lazy_fired,command.invoked) into four widgets: a stat card with the pool-mode rate as percentage + classification, a line chart of Sleeper requests by outcome over 7d, a bar chart of cron fires by cron over 30d, and a top-10 commands list by 30d invocation count. New_safe_metrics_query/_safe_metrics_totalhelpers defensively shield every dashboard handler so a metrics-backend hiccup returns an empty-state shape rather than breaking the page.
v2.5 (all-play standings)
- ✅
/commish-all-play— full all-play standings: every team's record as if it played every other team every week (beat everyone who scored less, lose to everyone who scored more; equal scores half-wins), ranked by all-play win%. The honest "who's actually been strongest" view that strips schedule luck out of the head-to-head standings. Reuses the tested all-play math already inside/commish-luck(no new computation, no new external API) and gates on the existingweekly_resultsfeature key (Sleeper + Fleaflicker; Fantrax has no weekly scores and gets a named refusal). Picked as the Phase-4 expansion after live-probing the remaining external platforms came up empty (see below). - ❌ No new external platform this cycle. A fresh live-probe (2026-06-27) reconfirmed the blockers: MFL issues a 302 redirect from its central host to a per-league sharded home host (
api.myfantasyleague.com→www49.myfantasyleague.comfor one probed league; leagues spread acrosswww1..www60+), which a fixedproxy_domains_requestedallow-list can't express; ESPN fantasy still needs a productionctx.httpreachability probe (lm-api-reads.fantasy.espn.com) before committing, and private leagues needespn_s2/SWIDcookies the sandbox can't hold; Yahoo remains OAuth-blocked.
v2.4 (one-tap live-scores refresh)
- ✅
/commish-scores🔄 Refresh button — live scores move during games, so the scores view now carries a one-tap Refresh that re-pulls without retyping the command. It reuses the week-hubscoresaction (commish:wk:scores:<league_id>) — no new component handler, no new capability — and re-attaches itself on every refresh so the message stays refreshable. State is encoded in thecustom_id(league ref parsed from the end, colon-safe), and a stale click whose league is no longer linked gets a named refusal.
v2.3.1 (tune-up: SDK update + drift/honesty hardening)
- ✅ SDK ceiling
<0.8.0→<0.9.0after diffing 0.7.1 → 0.8.2 (purely additive:ctx.ws,secret_auth/authhttp kwargs, dashboardquarterwidth — all behind caps we don't request; the modules Commish uses are byte-identical). Honesty fixes:/commish-injuriesand the injury cron now use the char-budget join + an honest "Showing N of M" footer instead of a silent[:25]slice with a lying count;/commish-draftand/commish-playoffsdistinguish a Sleeper outage from a genuinely-empty state viano_draft/no_bracketsentinels. Deadline:comp_setup_channeldefers before its blocking KV work. KV: completed-week caches expire after a 150-day season-length TTL (prevents cross-season stale reads on Fleaflicker/Fantrax, whose league IDs persist across seasons, and bounds key growth).
v2.3 (NFL scoreboard)
- ✅
/commish-nfl— live NFL game scoreboard from ESPN's public site API (site.api.espn.com), bucketed In progress → Upcoming → Final. Platform-agnostic (no linked league, like/commish-trends). Chosen after an empirical live-probe sweep; a full ESPN fantasy platform adapter stays deferred (private leagues needespn_s2/SWIDcookies, and production datacenter-proxy reachability needs actx.httpprobe first).
v2.2 (interactive week hub)
- ✅
/commish-weekquick-action buttons — 📊 Standings / 📈 Rankings / 🏈 Live scores jump to the full view in one click, turning the snapshot into a discovery surface. State encoded incustom_id(parsed from the end, since league refs contain colons); Live scores is platform-gated; stale-league / unsupported-platform clicks get named, actionable refusals. Newweek_hub:<action>component metric.
v2.1.1 (multi-platform regression-hardening)
- ✅ Audit-driven fix sweep (see
REGRESSION-REPORT.md): Fleaflicker BYE detection (the lone-survivor convention) no longer corrupts luck / record-book / high-score views; transaction- and injury-dedup KV keys are now per-league (multi-Sleeper-league servers no longer cross-contaminate); waiver/start-sit crons are platform-correct; playoff round labels derive from distance-to-final; plus never-raise hardening on adapter routes and the lazy-cron sweep. 13 new foreign-platform / multi-league regression tests. SDK pin ceiling raised to<0.8.0after diffing 0.7.x (purely additive).
v2.1 (Fantrax + feature matrix)
- ✅ Fantrax support via the same provider seam (
fantrax:<id>refs). Standings, all-mode rankings, weekly matchup schedules, the week snapshot and rosters work; Fantrax's public fxea API exposes no weekly scores or transactions (live-probed — the score-bearing internal API requires a login session), so score-derived features are gated. Player names resolve through a second embedded catalog (make refresh-playersbundles both; the live Fantrax id map is 1.7 MB, over the proxy cap). Alphanumeric team ids map to stable ints via sorted order. The binary "Sleeper-only" guards became a per-platform feature matrix so each platform gets exactly what its API can power.
v2.0 (multi-platform era — Fleaflicker)
- ✅ Provider abstraction + Fleaflicker support. Composite league refs (
fleaflicker:<id>) route through an adapter inside the fetch layer that returns Sleeper-shaped data, so every existing builder, cache and command works unchanged. Supported on Fleaflicker: matchups, live scores (with real yet-to-play counts), standings, rankings (all-mode), week, luck, record book, h2h, streaks, rosters (inline player names via FetchRoster — no embedded catalog needed) and the content crons. Sleeper-only commands explain themselves politely. New/commish-setup-fleaflicker;www.fleaflicker.comadded to the proxy allow-list. Next candidates per the platform-feasibility research: ESPN (read-only), then MFL; Yahoo is structurally blocked (OAuth secret + rotating refresh tokens can't live in the sandbox).
v1.13 (interactive UX)
- ✅ First message components.
/commish-roster(no arg) shows a clickable team picker;/commish-setup's confirmation carries a one-click "Post league updates in this channel" button (mapsgeneral, the universal fallback — a fresh install is fully configured in one click);/commish-helpis categorized into five titled groups with a 3-step quick start for un-setup servers. Newcomponent.invokedmetric. Built on SDK 0.6.xon_component(prefix=...)with all state encoded in custom_ids (the platform's interaction events don't carry select-menu values, so buttons it is).
v1.12 (SDK rename migration + audit hardening)
- ✅ Migrated to
yourbot-sdk0.6.x — the platform SDK was renamed frommmo-maid-sdkat 0.6.0 (the platform itself rebranded MMO Maid → YourBot.gg). Imports, requirements pins, the pre-flight validator, the Makefile dev loop (yourbot dev --watch) and all docs updated; the old import name only survives via a deprecated shim. - ✅ Completed-week KV caches (
cache:matchups,cache:trades) — season-wide commands and the Season-summary dashboard page no longer re-walk every week over HTTP per view; the dashboard page alone was exceeding the proxy's 30 req/min budget from ~week 9 onward. - ✅ Lazy-cron hardening — the on_ready sweep moved to a daemon thread (it ran synchronously before the interaction handler that woke a pool worker, eating the 3-second ack budget), and overdue fires now take an atomic per-slot KV claim so two pool workers can't double-post.
- ✅ Multi-league state scoping — the rostered-player cache and playoff-clinching dedup keys are now league-scoped; previously
/commish-switchleaked one league's state into another's posts. - ✅ Embed overflow guards —
/commish-trade-history,/commish-waiver-claimsand/commish-transactionsnow truncate to a ~3900-char budget with honest footers instead of silently failing past Discord's 4096-char description cap. - ✅ A dozen smaller fixes — waiver-reminder and power-rankings crons gated to the regular season; week 18 recognized as regular season; transactions dedup list trims oldest-first; command metric recorded after the handler (not inside the 3s ack window); Sleeper outages no longer reported as "no transactions yet"; standings-leader widget uses the standings sort; BYE entries excluded from the week-high-score widget; draft renderer tolerates missing pick numbers; metrics path tags strip query strings; per-player injury keys expire after 90 days.
Deferred (post-v1.0, conditional)
- Screenshots. No Discord-embed screenshots have ever shipped with the repo; capturing them requires a real Discord install with live data and is a manual step, not something this CLI-driven release can do. Tracking as a future PR rather than blocking v1.0 on it.
- Pool-mode server-side cron migration. Still conditional on observing pool-mode worker assignment in production — under SDK v0.5.2+, lifecycle hooks (
on_ready) work in pool mode but@plugin.cron/@plugin.scheduledon't fire. If pool mode actually becomes the default for this plugin's traffic profile, the nine scheduled posts will need to migrate to manifest-declared server-side crons. The manual slash-command path is unaffected.
Out of scope
- Yahoo Fantasy — requires an OAuth authorization-code flow with a client secret and rotating refresh tokens, none of which the sandbox can hold. Revisit if the platform ships an OAuth broker or
ctx.secrets-based token exchange. - NFL.com Fantasy — no league-scoped API exists anymore.
- Anything requiring server-side processing the YourBot.gg platform doesn’t offer.
Quick start (development)
# 1. Clone & install
git clone https://github.com/your-org/commish.git
cd commish
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt -r requirements-dev.txt
# 2. Tests
python -m pytest -q
# 3. Pre-flight validation (also runs in CI)
python scripts/validate_plugin.py .
# 4. Local dev loop (hot-reload + mock host)
yourbot dev --watch
yourbot dev hot-reloads on file change; add an events.yaml to fire sample events against a MockContext (without one the dev host only reloads — this repo doesn't ship one).
Release process
Releases are tagged on main with semver tags (v1.2.3), which triggers .github/workflows/release.yml to validate, test, build the upload zip, and attach it to the GitHub release.
# 1. Bump manifest.json "version" and update CHANGELOG.md
# 2. Verify locally
make release # refreshes the embedded player catalog (mutates __main__.py),
# validates, tests, builds dist/commish-<version>.zip.
# Commit the refreshed catalog before tagging — the target
# aborts if __main__.py is dirty at zip time.
# 3. Commit, tag, push
git commit -am "Release v0.2.0"
git tag v0.2.0
git push && git push --tags
The tag's version (v0.2.0 → 0.2.0) must match manifest.json's version field; CI rejects the release otherwise.
License
MIT — see LICENSE.
Have a question?
Ask the developer of Commish directly. Sign in with Discord to send your question. You'll be notified when they reply.
Sign in to ask the developer →Ready to install Commish?
Sign in with Discord, set up your server and add it in minutes.
Install on your server →