open-leagues

CLI

A script with three reads and one gated write. A terminal can inspect a league and place a wager without the browser app.

What it dispatches

There is no open-leagues binary. The CLI is a script and refuses anything outside its slice; the full surface is MCP.

shell
bun scripts/ledger.mjs --help
bun scripts/ledger.mjs --list
VerbKindRequired flags
getEventsread--league [--limit --sinceWeek]
getLeagueFactsread--league --week
getAgentContextread--league --user
placeWagerwrite--write --user --league --matchup --kind --side --line --stake

mintToken is not a catalog verb. It lets a self-hoster mint a token without the browser app.

Anything else is refused by name: “…is a catalogued read but this CLI slice only dispatches getEvents, getLeagueFacts, and getAgentContext”, or “…is mutating and is not dispatched from this CLI.”

Real invocations

Flags are --key value, or one --json blob with the same fields.

reads · JSON to stdout
bun scripts/ledger.mjs getEvents       --league lg_wiffl --limit 20
bun scripts/ledger.mjs getLeagueFacts  --league lg_wiffl --week 1
bun scripts/ledger.mjs getAgentContext --league lg_wiffl --user usr_…

# same fields as one argument
bun scripts/ledger.mjs getEvents --json '{"leagueId":"lg_wiffl","limit":20}'
Live reads and the write need DATABASE_URL pointing at the app’s Postgres. bun has no Vite import.meta.glob, so it cannot migrate the PGLite fallback. --help and --list need no database.

Reading the purse

getAgentContext returns the three numbers any spend must respect, in one round trip.

remaining
$86
at risk
$12
spendable
$74
faabBudget $100 · wagerCap $25 · exposureCap $40 · bookLocked false

spendable is max(0, remaining − atRisk). placeWager validates against spendable, so an unsettled ticket is not spendable money.

Not verbs

tick and tickAllLeagues are the league clock, driven by cron or the in-process ticker. Dispatch rejects them by name on both paths: “tick is a cron clock, not a tool”.