@claudinho/core
v0.13.1
Published
The engine behind Claudinho: the domain model, the provider adapters (ESPN), live scores and standings for the competition you follow (16 supported), the bundled 2026 World Cup schedule with its knockout bracket and read-only Polymarket sidecar. Independe
Maintainers
Keywords
Readme
@claudinho/core ⚽
The engine behind Claudinho: the domain model, the provider adapters (ESPN), live scores
and standings for the competition you follow (16 supported), the bundled 2026 World Cup
schedule with its knockout bracket and read-only Polymarket sidecar. Claudinho puts live
football in your dev environment for the competition you follow: the World Cup, the Premier
League, LALIGA, the Champions League and 12 more. This package powers
@claudinho/cli and
@claudinho/mcp.
⚠️ Not affiliated with FIFA, any confederation, league or club, or Anthropic. Nor endorsed by or connected to any of them. An independent, open-source fan project. Facts + emoji flags only.
Install
npm i @claudinho/coreIt needs Node.js 22 or later. That is the oldest line it runs on, not a security level: run a patched release of the Node 22 or 24 line.
What's inside
- Domain model:
Match(incl.venue/city/country),Team(a displaycode, aname, aflaggenerated from a nation's name, absent for a club, and the provider's stableidsuch asespn:359when the team came from a live feed),Stage(the tournament rounds,REGULARfor a league's season,LEAGUEfor a cup's league phase,POfor play-offs,OTHERwith the provider's own words instageLabel),Status,PunditPick,LedgerRow ProviderAdapter: the swappable data-vendor interface (fetchByDate,fetchLive, optionalfetchWindow/fetchStandings);EspnAdapterincluded. An adapter serves exactly one competition and states it (competition, required); a provider that knows which season a response belongs to attaches it to the returned array (attachFetchMeta, read withfetchMeta), with the seasons its parts stated (seasons) and the records it left out (omitted). A window whose parts state two seasons is refused unless it is askedacrossSeasons, which three callers that merge nothing do (the live read, and off the bundled competition the dated read and the match refresh);getNextFixtureForTeam,getBracket,getMatchesForDate,getLiveMatches,getMatchByIdandmarketFixtureForTeamsaypartialwhen the read behind them was not whole (dayAttributionand the "none read" sentences in the card builders say what that means for a day), andverdictExtras/verdictNotice/verdictQualifiersturn a result's verdict into a key and a sentence- Static schedule: the World Cup's 104 fixtures (groups, venues, host cities, kickoffs) bundled (the other competitions' schedules are read from the feed); query with
allFixtures,fixturesByDate(groups by your timezone),fixturesByTeam,fixturesByGroup,nextFixtureForTeam,groups - Live overlay:
makeAdapter(source, { competition }),getMatchesForDate,getLiveMatches,mergeLive(static base + live state, with graceful degradation). The competition is resolved once by the caller (resolveCompetition(explicit, env, saved): an alias or a slug, the first present source deciding (the flag, the environment, the saved choice the caller read withreadUserConfig); with none it answersnone, nothing chosen, never a default; anything else is refused with the aliases; core reads no environment and no file, the caller hands them in), and said bymodeLineandselectionExtras; the bundled schedule is merged only for its own competition and edition (bundleApplies) - The supported set:
SUPPORTED, ONE table of the sixteen competitions (slug, alias, name, teams, kind, season name, standings shape, bracket and markets capabilities, cadence);deriveTables(table)builds every written view from it, andentryOf,capabilitiesOf,competitionLabel,listCompetitionstake a table, so a new row reaches every consumer - Standings:
getStandingsfetches authoritative, cumulative tables from the provider (GroupStandings: a table has a key,A,A1,A-BorLEAGUE, and every table but a lettered group alabel), failing closed when none is available, and sayingincompletewhen the provider sent a table that could not be read: the compatible bundled World Cup scope may return a degraded roster-at-zero, while custom competitions return empty + degraded rather than borrowing those teams;computeStandingsderives a table from a set of matches (points / GD / GF tiebreak) - Helpers: emoji flags (
nationToFlag), TZ-aware time (formatKickoff,formatDate,formatTime,countdown,localDate), location strings (matchLocation), localized commentary flair (matchFlavor,flavorsForfor a list with no phrase twice,matchFlairsfor a list's flair slots, the cries included, andmatchFlairfor one line /FlavorLevel; the team rally cries,rallyCryFor/RALLY_CRIES, the pin deciding between two throughisPinnedSide), validators (isValidDate,isValidTimeZone) - Prediction-market signals (sidecar): read-only market signals kept separate from
Match: theMarketSignal/MarketProvidermodel, thePolymarketProvider(public Gamma data only: no auth/trading/links; event slugs auto-derived per fixture, validation fails closed), aFakeMarketProvider,makeMarketProvider,getMarketSignal/getMarketSignals, theisReliableMarketSignalgate, and approved-copy formatters (marketFavoriteText,marketProbabilityText,marketBlock). Informational only, never betting advice. - Shareable snippets:
formatShareSnippetbuilds pure, deterministic, plain-text match cards (composingMatch+ the market copy bank);formatShareTabledoes the same for standings, one table or all of them (facts + emoji flags only, no market line). For the CLI'ssharecommand and MCP/site reuse. The non-affiliation disclaimer is non-optional (DISCLAIMER, the one sentence every surface imports;disclaimerLine(host)adds a listing host's clause); market lines come from the approved bank.
Example
import { allFixtures, nextFixtureForTeam, formatKickoff } from '@claudinho/core';
const next = nextFixtureForTeam('MEX');
console.log(next?.home.flag, 'vs', next?.away.flag,
formatKickoff(next!.kickoff, { tz: 'America/Mexico_City', locale: 'es' }));License
MIT © 2026 Arturo Garrido · source & issues
Built while watching the games. #VibingLaVidaLoca ⚽
