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.
bun scripts/ledger.mjs --help bun scripts/ledger.mjs --list
| Verb | Kind | Required flags |
|---|---|---|
| getEvents | read | --league [--limit --sinceWeek] |
| getLeagueFacts | read | --league --week |
| getAgentContext | read | --league --user |
| placeWager | write | --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.
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}'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.
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”.