@claudinho/core 0.11.1 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -313,16 +313,21 @@ declare function formatDate(iso: string, opts?: FormatOpts): string;
313
313
  declare function formatTime(iso: string, opts?: FormatOpts): string;
314
314
  /** What {@link countdown} says of a kickoff that has passed (the statusline prints it as it is: English, by its rule). */
315
315
  declare const COUNTDOWN_NOW = "now";
316
- /** Compact human countdown until kickoff: "3d4h", "2h10m", "45m", or {@link COUNTDOWN_NOW}. */
317
- declare function countdown(iso: string, from?: Date): string;
316
+ /**
317
+ * Compact human countdown until kickoff: "3d4h", "2h10m", "45m", or {@link COUNTDOWN_NOW}, from `from`: the clock of
318
+ * the request or render that says it, which every caller hands in (no default: the wall clock is never read here).
319
+ */
320
+ declare function countdown(iso: string, from: Date): string;
318
321
  /**
319
322
  * The countdown as the reader's phrase, ONE rule for every localized line that
320
323
  * says it (CLI `next`, the MCP match line): "in 3d4h" (`next.in`), or, for a
321
324
  * kickoff that has passed (a stale record, the bundle's skeleton during an
322
325
  * outage), the language's own word for now (`countdown.now`), never the
323
- * English word inside a translated "in".
326
+ * English word inside a translated "in". Relative to the REQUEST's clock
327
+ * (`from`), the one every other read of the request takes and every caller
328
+ * passes: the wall clock is never read here.
324
329
  */
325
- declare function countdownPhrase(lang: string | undefined, iso: string, from?: Date): string;
330
+ declare function countdownPhrase(lang: string | undefined, iso: string, from: Date): string;
326
331
  /**
327
332
  * The calendar date (YYYY-MM-DD) of a kickoff in the target timezone, or ''
328
333
  * when the input can't be parsed — an unfileable fixture matches no date rather
@@ -1384,6 +1389,15 @@ interface LiveResult {
1384
1389
  */
1385
1390
  served?: readonly string[];
1386
1391
  }
1392
+ /**
1393
+ * THE clock of a request: the one its caller gave (a test's, a command's
1394
+ * context), else the adapter's read clock ({@link clockOf}: the clock the
1395
+ * adapter's reads and its cooldown count by, the wall clock in production).
1396
+ * A surface reads it ONCE per request, after resolving its adapter, and hands
1397
+ * it to every read and every rendering of the request, so a lookup and a
1398
+ * countdown in one answer agree on "now".
1399
+ */
1400
+ declare function requestClock(adapter: ProviderAdapter, given?: Date): Date;
1387
1401
  /** Human label for a live-data provider name (attribution). Text only. */
1388
1402
  declare function liveSourceLabel(source: string): string;
1389
1403
  /**
@@ -3054,6 +3068,314 @@ declare const FAN_PROJECT = "Independent fan project";
3054
3068
  */
3055
3069
  declare function disclaimerLine(host?: string): string;
3056
3070
 
3071
+ /**
3072
+ * Where a selection came from: the command line's `--competition` or a tool's
3073
+ * `competition` argument (`flag`), `CLAUDINHO_COMPETITION` (`env`), or the
3074
+ * user's saved choice (`saved`: the config file `claudinho follow` writes).
3075
+ * Nothing else chooses: with none of the three there is no competition
3076
+ * (`{ kind: 'none' }`), never a default.
3077
+ */
3078
+ type ChosenBy = 'flag' | 'env' | 'saved';
3079
+ /** A competition a request is for, and where that choice came from. */
3080
+ interface SelectedCompetition {
3081
+ readonly kind: 'selected';
3082
+ /** The provider's slug the request asks for. */
3083
+ readonly slug: string;
3084
+ /** The table's alias; absent for a raw slug. */
3085
+ readonly alias?: string;
3086
+ /** What a surface names it: the table's name, or a raw slug as it is. */
3087
+ readonly name: string;
3088
+ readonly chosenBy: ChosenBy;
3089
+ /** A raw slug the table does not hold: the escape hatch, labelled. */
3090
+ readonly experimental: boolean;
3091
+ }
3092
+ /**
3093
+ * What the edge resolved: a competition; a value that is neither an alias nor
3094
+ * a slug, REFUSED with the aliases (an unknown value is never a request); or
3095
+ * `none`: nothing was chosen (no flag, no environment, no saved choice). The
3096
+ * finished World Cup is not a default anyone falls into without choosing.
3097
+ */
3098
+ type CompetitionSelection = SelectedCompetition | {
3099
+ readonly kind: 'refused';
3100
+ readonly value: string;
3101
+ readonly aliases: string[];
3102
+ /** The source that gave the refused value (a flag or the environment: the file's reader refuses its own). */
3103
+ readonly chosenBy: ChosenBy;
3104
+ } | {
3105
+ readonly kind: 'none';
3106
+ };
3107
+ /**
3108
+ * The ONE constructor of a selected competition: a slug and where it was
3109
+ * chosen, described by the table (its alias and name) or, for a slug the
3110
+ * table does not hold, as itself and experimental. The resolver builds every
3111
+ * selection through it; an edge that already holds a slug (a config built
3112
+ * without the resolver) describes it through it too, never by hand.
3113
+ */
3114
+ declare function selectedCompetition(slug: string, chosenBy: ChosenBy): SelectedCompetition;
3115
+ /**
3116
+ * THE EDGE. The one function that decides which competition a request is for,
3117
+ * from the values its edge hands in: the flag (`--competition`, a tool's
3118
+ * `competition` argument), then the environment (`CLAUDINHO_COMPETITION`), then
3119
+ * the saved choice (the config file's `competition`, read at the edge through
3120
+ * core's one reader: `readUserConfig`). The FIRST present source decides (an
3121
+ * empty string is absent): a present value that is refused is refused, even
3122
+ * when a lower source holds a valid one (what was asked for is not answered
3123
+ * with something else). With no source present, `none`: nothing is chosen,
3124
+ * and every competition-answering surface says so instead of answering.
3125
+ *
3126
+ * Each value is an alias (`premier-league`), a slug in the table (`eng.1`), or
3127
+ * a raw ESPN slug the table does not hold (`fifa.friendly`, `esp.copa_del_rey`:
3128
+ * lower-case segments of letters, digits and underscores joined by dots, 64
3129
+ * units at most; experimental);
3130
+ * anything else (`foo`, `ENG.1`, a space) is refused with the aliases.
3131
+ *
3132
+ * Core reads NO environment and no file: the edges pass them. It is called
3133
+ * where a request ENTERS (the CLI's option resolution, the MCP server's
3134
+ * request) and nowhere else. From there the competition travels as a value
3135
+ * (on the config, on the adapter), so nothing further down can answer for a
3136
+ * different competition halfway through. `core/test/selection-identity.test.ts`
3137
+ * fails if a call appears anywhere else.
3138
+ */
3139
+ declare function resolveCompetition(explicit?: string, env?: string, saved?: string): CompetitionSelection;
3140
+ /**
3141
+ * The line every competition-answering text answer prints after its header:
3142
+ * the competition's name, then where the choice came from when it was the
3143
+ * flag or the environment (`from the command line`; on MCP, `from the
3144
+ * request`; `from the environment`), then `experimental` for a raw slug. A
3145
+ * saved choice prints the name alone. The name is not localized; the rest is.
3146
+ * Empty for a selection that is not a competition.
3147
+ */
3148
+ declare function modeLine(selection: CompetitionSelection, lang?: string,
3149
+ /** What a flag is on this surface: the command line's, or a tool request's argument. */
3150
+ flag?: 'command' | 'request'): string;
3151
+ /** The structured form of a selection: what `--json` and MCP `data` carry. */
3152
+ interface CompetitionKey {
3153
+ readonly slug: string;
3154
+ readonly alias?: string;
3155
+ readonly name: string;
3156
+ readonly chosenBy: ChosenBy;
3157
+ readonly experimental?: true;
3158
+ }
3159
+ /**
3160
+ * The selection as ONE structured key, `competition`, for every
3161
+ * competition-answering structured answer (CLI `--json`, MCP `data`): the
3162
+ * alias only when there is one, `experimental` only when true; `null` when
3163
+ * nothing is chosen (beside the `noCompetition` verdict: see
3164
+ * {@link selectionVerdict}). Nothing for a refused value (it is an error, never
3165
+ * an answer). Every emit site spreads this; none builds the key by hand.
3166
+ */
3167
+ declare function selectionExtras(selection: CompetitionSelection): {
3168
+ competition?: CompetitionKey | null;
3169
+ };
3170
+ /**
3171
+ * The verdict a selection states about every answer made under it: with
3172
+ * nothing chosen, `noCompetition` (the first replacing verdict: its sentence
3173
+ * stands instead of any answer, and its key goes beside `competition: null`);
3174
+ * nothing otherwise. A surface hands THIS to `verdictNotice`/`verdictExtras`,
3175
+ * never an object it builds itself.
3176
+ */
3177
+ declare function selectionVerdict(selection: CompetitionSelection): VerdictSource;
3178
+ /**
3179
+ * Why a selection is not a competition, in the reader's language; undefined
3180
+ * for a competition. A refused value names the value (bounded) and the
3181
+ * aliases; nothing chosen is the CLI's sentence (`claudinho follow <alias>`,
3182
+ * `follow --list`): MCP answers nothing chosen with the `noCompetition`
3183
+ * verdict's own sentence (`verdictNotice`), never this one. The CLI raises it
3184
+ * as an input error, MCP a refused value as a tool error, before any request.
3185
+ */
3186
+ declare function selectionRefusal(selection: CompetitionSelection, lang?: string): string | undefined;
3187
+ /** The competition whose schedule ships bundled in the clients: the World Cup. */
3188
+ declare const BUNDLE_COMPETITION = "fifa.world";
3189
+ /**
3190
+ * The edition the bundled schedule describes, read from the schedule itself
3191
+ * (the year of its first kickoff) rather than written down a second time.
3192
+ */
3193
+ declare function bundleSeasonYear(): number;
3194
+ /**
3195
+ * Does the bundled schedule describe what this request is about?
3196
+ *
3197
+ * The competition is an ARGUMENT — the caller's adapter states it — never a
3198
+ * default. Off the bundle, every path built on the skeleton (date merge,
3199
+ * `match <id>`, `next`, the bracket, the knockout-fixture cache, the market
3200
+ * fixture) must not read it: an empty foreign day once showed 104 World Cup
3201
+ * fixtures with foreign attribution (audit A03).
3202
+ *
3203
+ * The bundle is ONE EDITION of its competition. When the provider reported the
3204
+ * season a response belongs to and it is a different one, the bundle does not
3205
+ * describe that response. With no provider answer (offline, degraded) there is
3206
+ * nothing to compare, and the bundle stays what it is: the 2026 edition.
3207
+ */
3208
+ declare function bundleApplies(competition: string, season?: SeasonInfo): boolean;
3209
+ /**
3210
+ * The competitions that HAVE NO BRACKET: league seasons with no knockout tie of
3211
+ * their own, where "no bracket" is the answer, not a gap. Written down in the
3212
+ * supported table (`bracket: 'not-applicable'`), one fact each, never inferred
3213
+ * from the standings shape (`STANDINGS_SHAPE` says
3214
+ * how a table is read, not whether a knockout follows it: `uefa.champions` and
3215
+ * `mex.1` read as one league table and both have one).
3216
+ * - `eng.1`: the Premier League; a 38-round league, no play-off.
3217
+ * - `esp.1`: LaLiga; a 38-round league, no play-off.
3218
+ * Deliberately NOT here: `ita.1` (championship and relegation play-offs since
3219
+ * the FIGC's May 2026 decision), `ger.1` (the relegation play-off), `mex.1`
3220
+ * (the Liguilla), `uefa.champions` (a knockout after the league phase). A
3221
+ * competition joins by a written fact. A derived view, kept for its readers (it
3222
+ * is exported); {@link bracketCapability} reads the row itself.
3223
+ */
3224
+ declare const NO_BRACKET: ReadonlySet<string>;
3225
+ /**
3226
+ * What `bracket` is for a competition, one of three values, read from its ROW
3227
+ * (one row, one fact: a listing, the README matrix and the command cannot
3228
+ * disagree):
3229
+ * - `offered`: the row offers it (only the bundled competition can:
3230
+ * `deriveTables` refuses any other row that does);
3231
+ * - `inapplicable`: the row says the competition has none (`not-applicable`);
3232
+ * - `unsupported`: it may have one, and it is not offered yet; also a slug
3233
+ * the table does not hold.
3234
+ */
3235
+ declare function bracketCapability(competition: string, table?: readonly CompetitionEntry[]): 'offered' | 'inapplicable' | 'unsupported';
3236
+
3237
+ /**
3238
+ * The ambient view: what `claudinho ambient --json` prints, ONE JSON object on
3239
+ * one line, for a program that shows the statusline and the hook itself (the
3240
+ * Claude Code plugin's band, its toasts and its prompt context). A type and
3241
+ * its documentation only: the CLI builds it from its local cache (the
3242
+ * statusline's reader, in `packages/cli`), and core reads no cache and no
3243
+ * environment for it.
3244
+ *
3245
+ * Three shapes reach a reader, and the command always exits 0:
3246
+ * - nothing chosen (no `--competition`, no `CLAUDINHO_COMPETITION`, no saved
3247
+ * choice): the selection's structured twin, `competition: null` beside the
3248
+ * verdict that says nothing is chosen, and `line` (the statusline's
3249
+ * first-run line); no cache is read and no refresher started;
3250
+ * - a value that is no competition, or a failure anywhere: `line` (the
3251
+ * statusline's empty line) and `empty: true` alone, no view;
3252
+ * - a selected competition: an {@link AmbientView}.
3253
+ */
3254
+
3255
+ /**
3256
+ * A live match as the ambient view lists it: the sealed record, as the other
3257
+ * `--json` twins serialize a `Match` (`status`, the `score` object, `shootout`
3258
+ * after a shootout, `Team.id` where the cache holds it, a nation's generated
3259
+ * `flag`, no `flag` key on a club), with no `events` key (the ambient reader
3260
+ * never seals a timeline), and two booleans about the reader's own team:
3261
+ */
3262
+ type AmbientMatch = Match & {
3263
+ /**
3264
+ * A side of it is the ambient PREFERENCE's (`pick`): `CLAUDINHO_TEAM`'s
3265
+ * code, else the saved pin. The picked matches come first in the list.
3266
+ */
3267
+ readonly picked: boolean;
3268
+ /**
3269
+ * A side of it is the SAVED pin's (`claudinho follow <alias> --team`), by
3270
+ * the pin's id when it has one (a club), by its code when it has none (the
3271
+ * World Cup's nations); whatever `CLAUDINHO_TEAM` prefers.
3272
+ */
3273
+ readonly pinned: boolean;
3274
+ /**
3275
+ * The hook's own line for this record, as `claudinho hook` prints it (the
3276
+ * names, pinned to the World Cup's roster on the bundle, each with its flag
3277
+ * where flags are on and the side has one, the scoreline with a shootout's
3278
+ * tally, the minute token: `67'`, `half-time`, `live`). A program says it,
3279
+ * never composes one.
3280
+ */
3281
+ readonly line: string;
3282
+ };
3283
+ /** The ambient view of a selected competition, its keys in this order. */
3284
+ interface AmbientView {
3285
+ /**
3286
+ * The statusline's line, as `claudinho prompt` prints it (the same reader,
3287
+ * the same options), fitted to `--columns N` when given: whole matches are
3288
+ * kept while they fit and the rest counted in the `+N` (or `+more`) suffix,
3289
+ * which is never cut while the width holds it; below the suffix's own width
3290
+ * the line is the width's cut, never wider than asked.
3291
+ */
3292
+ readonly line: string;
3293
+ /**
3294
+ * The line is the one long-lived line, the edition's sign-off (the World
3295
+ * Cup's, once every bundled window has closed): nothing changes until the
3296
+ * user follows another competition, so a program reads it slowly. Said of
3297
+ * the line before any fit: a program reads the pace from this, never from the
3298
+ * text, which `--columns` may have cut.
3299
+ */
3300
+ readonly idle: boolean;
3301
+ /**
3302
+ * The line says nothing is known (`⚽ —`): no live match read, no window to
3303
+ * sync in, nothing to count down to. A program shows nothing for it. Said
3304
+ * of the line before any fit: a program reads the visibility from this,
3305
+ * never from the text.
3306
+ */
3307
+ readonly empty: boolean;
3308
+ /** The hook's block, as `claudinho hook` prints it while a match is live; `null` when it prints nothing. */
3309
+ readonly context: string | null;
3310
+ /**
3311
+ * The live matches the statusline reads (a believed snapshot's, inside the
3312
+ * display window), the preference's first. `complete` says the scan was
3313
+ * whole and `total` is exact only then; an empty list with `complete: false`
3314
+ * is "not known". An empty list is "nothing on" ONLY beside `current: true`:
3315
+ * with `current: false` (no snapshot, a stale one, a stamp the reader does
3316
+ * not believe, a degraded one, one whose read was not whole or that does
3317
+ * not say, the syncing window) it says nothing about
3318
+ * play, a match may be on (`line` is the authority: it prints
3319
+ * `live · syncing…` in the window), and a program must never read
3320
+ * `complete && items.length === 0` alone as "nothing on". A degraded
3321
+ * snapshot (the last read failed) may still carry a live record: it is
3322
+ * listed, best effort, as the line shows it, and is never current.
3323
+ */
3324
+ readonly live: BoundedList<AmbientMatch>;
3325
+ /**
3326
+ * The live list is a believed snapshot's inside its display window (its
3327
+ * stamp's age, by the reader's own rule, is finite and short of the window),
3328
+ * the snapshot is not degraded (a degraded one means the fetch failed, not
3329
+ * that the feed said nothing: it is never current, whatever it carries), the
3330
+ * snapshot states that the read that filled its live slice was whole (a read
3331
+ * that was not, an unreadable record left out beside readable ones, is never
3332
+ * current; nor is a snapshot that does not say, written before 0.11.2, until
3333
+ * one refresh cycle rewrites it), and the line is not syncing: then, and
3334
+ * only then, an empty list means the feed said nothing is in play.
3335
+ */
3336
+ readonly current: boolean;
3337
+ /**
3338
+ * The fixture the statusline's countdown names (its resolved pairing; the
3339
+ * preference's first), whether or not a match is in play now; `null` when
3340
+ * there is none to count down to. A fixture ahead, never one in play.
3341
+ */
3342
+ readonly next: Match | null;
3343
+ /**
3344
+ * The ambient preference as the CLI holds it: `{ code }` from
3345
+ * `CLAUDINHO_TEAM`, the saved pin (`{ id?, code, name }`) when that is
3346
+ * unset, or `null`.
3347
+ */
3348
+ readonly pick: {
3349
+ readonly code: string;
3350
+ } | Pin | null;
3351
+ /**
3352
+ * Present (and `true`) when `CLAUDINHO_TEAM` is set to a value the hot path
3353
+ * cannot read offline (off the World Cup, anything but a three-letter code;
3354
+ * on it, a name its roster does not hold): `pick` is then `null`, never the
3355
+ * pin (the environment is the override).
3356
+ */
3357
+ readonly pickUnreadable?: true;
3358
+ /** The selection, as every `--json` answer carries it (core `selectionExtras`). */
3359
+ readonly competition: CompetitionKey;
3360
+ /** The snapshot says its last refresh failed (`false` with no snapshot). */
3361
+ readonly degraded: boolean;
3362
+ /** The provider the snapshot was read from, whatever its stamp; `null` with no snapshot. */
3363
+ readonly source: string | null;
3364
+ /**
3365
+ * When the snapshot was written (ISO 8601, re-emitted canonically), or
3366
+ * `null` with no snapshot or a stamp the reader does not believe (further in
3367
+ * the future than its clock-skew allowance: its live scores are not shown).
3368
+ */
3369
+ readonly updatedAt: string | null;
3370
+ /**
3371
+ * When the snapshot's live scores stop being shown (`updatedAt` plus the
3372
+ * statusline's display window), or `null` whenever `updatedAt` is.
3373
+ */
3374
+ readonly staleAfter: string | null;
3375
+ /** The non-affiliation sentence, core's `DISCLAIMER`. */
3376
+ readonly disclaimer: string;
3377
+ }
3378
+
3057
3379
  /** The two v1 snippet shapes. `social` is the rich card; `compact` is one terse line per match. */
3058
3380
  type ShareStyle = 'compact' | 'social';
3059
3381
  /** The project social tag — a distribution unit, default-on but removable. */
@@ -3544,172 +3866,6 @@ declare function isPlaceholderSide(team: Pick<Team, 'flag'>): boolean;
3544
3866
  /** The bundled knockout bracket topology (generated by gen:schedule). */
3545
3867
  declare function loadBracketTopology(): BracketTopology;
3546
3868
 
3547
- /**
3548
- * Where a selection came from: the command line's `--competition` or a tool's
3549
- * `competition` argument (`flag`), `CLAUDINHO_COMPETITION` (`env`), or the
3550
- * user's saved choice (`saved`: the config file `claudinho follow` writes).
3551
- * Nothing else chooses: with none of the three there is no competition
3552
- * (`{ kind: 'none' }`), never a default.
3553
- */
3554
- type ChosenBy = 'flag' | 'env' | 'saved';
3555
- /** A competition a request is for, and where that choice came from. */
3556
- interface SelectedCompetition {
3557
- readonly kind: 'selected';
3558
- /** The provider's slug the request asks for. */
3559
- readonly slug: string;
3560
- /** The table's alias; absent for a raw slug. */
3561
- readonly alias?: string;
3562
- /** What a surface names it: the table's name, or a raw slug as it is. */
3563
- readonly name: string;
3564
- readonly chosenBy: ChosenBy;
3565
- /** A raw slug the table does not hold: the escape hatch, labelled. */
3566
- readonly experimental: boolean;
3567
- }
3568
- /**
3569
- * What the edge resolved: a competition; a value that is neither an alias nor
3570
- * a slug, REFUSED with the aliases (an unknown value is never a request); or
3571
- * `none`: nothing was chosen (no flag, no environment, no saved choice). The
3572
- * finished World Cup is not a default anyone falls into without choosing.
3573
- */
3574
- type CompetitionSelection = SelectedCompetition | {
3575
- readonly kind: 'refused';
3576
- readonly value: string;
3577
- readonly aliases: string[];
3578
- /** The source that gave the refused value (a flag or the environment: the file's reader refuses its own). */
3579
- readonly chosenBy: ChosenBy;
3580
- } | {
3581
- readonly kind: 'none';
3582
- };
3583
- /**
3584
- * The ONE constructor of a selected competition: a slug and where it was
3585
- * chosen, described by the table (its alias and name) or, for a slug the
3586
- * table does not hold, as itself and experimental. The resolver builds every
3587
- * selection through it; an edge that already holds a slug (a config built
3588
- * without the resolver) describes it through it too, never by hand.
3589
- */
3590
- declare function selectedCompetition(slug: string, chosenBy: ChosenBy): SelectedCompetition;
3591
- /**
3592
- * THE EDGE. The one function that decides which competition a request is for,
3593
- * from the values its edge hands in: the flag (`--competition`, a tool's
3594
- * `competition` argument), then the environment (`CLAUDINHO_COMPETITION`), then
3595
- * the saved choice (the config file's `competition`, read at the edge through
3596
- * core's one reader: `readUserConfig`). The FIRST present source decides (an
3597
- * empty string is absent): a present value that is refused is refused, even
3598
- * when a lower source holds a valid one (what was asked for is not answered
3599
- * with something else). With no source present, `none`: nothing is chosen,
3600
- * and every competition-answering surface says so instead of answering.
3601
- *
3602
- * Each value is an alias (`premier-league`), a slug in the table (`eng.1`), or
3603
- * a raw ESPN slug the table does not hold (`fifa.friendly`, `esp.copa_del_rey`:
3604
- * lower-case segments of letters, digits and underscores joined by dots, 64
3605
- * units at most; experimental);
3606
- * anything else (`foo`, `ENG.1`, a space) is refused with the aliases.
3607
- *
3608
- * Core reads NO environment and no file: the edges pass them. It is called
3609
- * where a request ENTERS (the CLI's option resolution, the MCP server's
3610
- * request) and nowhere else. From there the competition travels as a value
3611
- * (on the config, on the adapter), so nothing further down can answer for a
3612
- * different competition halfway through. `core/test/selection-identity.test.ts`
3613
- * fails if a call appears anywhere else.
3614
- */
3615
- declare function resolveCompetition(explicit?: string, env?: string, saved?: string): CompetitionSelection;
3616
- /**
3617
- * The line every competition-answering text answer prints after its header:
3618
- * the competition's name, then where the choice came from when it was the
3619
- * flag or the environment (`from the command line`; on MCP, `from the
3620
- * request`; `from the environment`), then `experimental` for a raw slug. A
3621
- * saved choice prints the name alone. The name is not localized; the rest is.
3622
- * Empty for a selection that is not a competition.
3623
- */
3624
- declare function modeLine(selection: CompetitionSelection, lang?: string,
3625
- /** What a flag is on this surface: the command line's, or a tool request's argument. */
3626
- flag?: 'command' | 'request'): string;
3627
- /** The structured form of a selection: what `--json` and MCP `data` carry. */
3628
- interface CompetitionKey {
3629
- readonly slug: string;
3630
- readonly alias?: string;
3631
- readonly name: string;
3632
- readonly chosenBy: ChosenBy;
3633
- readonly experimental?: true;
3634
- }
3635
- /**
3636
- * The selection as ONE structured key, `competition`, for every
3637
- * competition-answering structured answer (CLI `--json`, MCP `data`): the
3638
- * alias only when there is one, `experimental` only when true; `null` when
3639
- * nothing is chosen (beside the `noCompetition` verdict: see
3640
- * {@link selectionVerdict}). Nothing for a refused value (it is an error, never
3641
- * an answer). Every emit site spreads this; none builds the key by hand.
3642
- */
3643
- declare function selectionExtras(selection: CompetitionSelection): {
3644
- competition?: CompetitionKey | null;
3645
- };
3646
- /**
3647
- * The verdict a selection states about every answer made under it: with
3648
- * nothing chosen, `noCompetition` (the first replacing verdict: its sentence
3649
- * stands instead of any answer, and its key goes beside `competition: null`);
3650
- * nothing otherwise. A surface hands THIS to `verdictNotice`/`verdictExtras`,
3651
- * never an object it builds itself.
3652
- */
3653
- declare function selectionVerdict(selection: CompetitionSelection): VerdictSource;
3654
- /**
3655
- * Why a selection is not a competition, in the reader's language; undefined
3656
- * for a competition. A refused value names the value (bounded) and the
3657
- * aliases; nothing chosen is the CLI's sentence (`claudinho follow <alias>`,
3658
- * `follow --list`): MCP answers nothing chosen with the `noCompetition`
3659
- * verdict's own sentence (`verdictNotice`), never this one. The CLI raises it
3660
- * as an input error, MCP a refused value as a tool error, before any request.
3661
- */
3662
- declare function selectionRefusal(selection: CompetitionSelection, lang?: string): string | undefined;
3663
- /** The competition whose schedule ships bundled in the clients: the World Cup. */
3664
- declare const BUNDLE_COMPETITION = "fifa.world";
3665
- /**
3666
- * The edition the bundled schedule describes, read from the schedule itself
3667
- * (the year of its first kickoff) rather than written down a second time.
3668
- */
3669
- declare function bundleSeasonYear(): number;
3670
- /**
3671
- * Does the bundled schedule describe what this request is about?
3672
- *
3673
- * The competition is an ARGUMENT — the caller's adapter states it — never a
3674
- * default. Off the bundle, every path built on the skeleton (date merge,
3675
- * `match <id>`, `next`, the bracket, the knockout-fixture cache, the market
3676
- * fixture) must not read it: an empty foreign day once showed 104 World Cup
3677
- * fixtures with foreign attribution (audit A03).
3678
- *
3679
- * The bundle is ONE EDITION of its competition. When the provider reported the
3680
- * season a response belongs to and it is a different one, the bundle does not
3681
- * describe that response. With no provider answer (offline, degraded) there is
3682
- * nothing to compare, and the bundle stays what it is: the 2026 edition.
3683
- */
3684
- declare function bundleApplies(competition: string, season?: SeasonInfo): boolean;
3685
- /**
3686
- * The competitions that HAVE NO BRACKET: league seasons with no knockout tie of
3687
- * their own, where "no bracket" is the answer, not a gap. Written down in the
3688
- * supported table (`bracket: 'not-applicable'`), one fact each, never inferred
3689
- * from the standings shape (`STANDINGS_SHAPE` says
3690
- * how a table is read, not whether a knockout follows it: `uefa.champions` and
3691
- * `mex.1` read as one league table and both have one).
3692
- * - `eng.1`: the Premier League; a 38-round league, no play-off.
3693
- * - `esp.1`: LaLiga; a 38-round league, no play-off.
3694
- * Deliberately NOT here: `ita.1` (championship and relegation play-offs since
3695
- * the FIGC's May 2026 decision), `ger.1` (the relegation play-off), `mex.1`
3696
- * (the Liguilla), `uefa.champions` (a knockout after the league phase). A
3697
- * competition joins by a written fact. A derived view, kept for its readers (it
3698
- * is exported); {@link bracketCapability} reads the row itself.
3699
- */
3700
- declare const NO_BRACKET: ReadonlySet<string>;
3701
- /**
3702
- * What `bracket` is for a competition, one of three values, read from its ROW
3703
- * (one row, one fact: a listing, the README matrix and the command cannot
3704
- * disagree):
3705
- * - `offered`: the row offers it (only the bundled competition can:
3706
- * `deriveTables` refuses any other row that does);
3707
- * - `inapplicable`: the row says the competition has none (`not-applicable`);
3708
- * - `unsupported`: it may have one, and it is not offered yet; also a slug
3709
- * the table does not hold.
3710
- */
3711
- declare function bracketCapability(competition: string, table?: readonly CompetitionEntry[]): 'offered' | 'inapplicable' | 'unsupported';
3712
-
3713
3869
  /**
3714
3870
  * Metadata that belongs to ONE fetch result.
3715
3871
  *
@@ -3911,4 +4067,4 @@ declare function lookAtEntry(path: string): FileEntry;
3911
4067
  */
3912
4068
  declare function lookAtOwnFile(path: string, maxBytes: number): OwnFile;
3913
4069
 
3914
- export { BRACKET_STAGE_ORDER, BUNDLE_COMPETITION, type BatchResolution, type BetweenEditions, type BoundedList, type BracketFormatOpts, type BracketMatchNode, type BracketMatchView, type BracketResult, type BracketShareCard, type BracketTopology, type BracketView, type BuildSignalInput, COMPETITION_KIND, COUNTDOWN_NOW, type Capabilities, type Capability, type ChosenBy, type ClubResolution, type CompetitionEntry, type CompetitionKey, type CompetitionKind, type CompetitionSelection, DEFAULT_COMPETITION, DEFAULT_COOLDOWN_MS, DEFAULT_FLAVOR, DEFAULT_MAX_AGE_MS, DISCLAIMER, type DayAttribution, type DerivedTables, EARLIER_RECORD_NOTE, EspnAdapter, type EspnAdapterOptions, FAN_PROJECT, FLAVOR_BANKS, FLAVOR_LEVELS, FLAVOR_MOMENTS, FakeMarketProvider, type FakeMarketProviderOptions, type FavoriteStrength, type FetchMeta, type FetchWindowOptions, type FileEntry, type Flair, type FlairOpts, type FlavorBank, type FlavorLevel, type Moment as FlavorMoment, type FlavorOpts, type FormatOpts, type GroupStandings, KNOCKOUT_EXTRA_TIME_MS, KNOWN_SOURCES, type KnockoutFixturesResult, LIVE_WINDOW_MS, type Lang, type LedgerRow, type ListedCompetition, type LiveReadResult, type LiveResult, MARKETS_SCOPE_NOTE, MARKET_COMPETITIONS, MAX_CONFIG_BYTES, MAX_COOLDOWN_MS, MAX_LABEL_COLUMNS, MAX_RESPONSE_BYTES, MAX_SCHEDULE_INDEX, type MapContext, type MarketFavorite, type MarketMapping, type MarketMappingTable, type MarketOutcome, type MarketOutcomeKind, type MarketProvider, type MarketSignal, type MarketSignalOptions, type MarketSignalsResult, type Match, type MatchByIdResult, type MatchEvent, type MatchShareCard, NO_BRACKET, type NextFixtureResult, type NoConfigReason, type Outcome, type OwnFile, type ParseResult, type Pin, PolymarketProvider, type PolymarketProviderOptions, type ProviderAdapter, type ProviderCapabilities, ProviderError, type ProviderErrorKind, type PunditPick, RALLY_CRIES, type RallyCry, type RallyPin, type ReadAccount, type ResolvedParticipant, ResponseTooLargeError, type Roster, SCHEDULE_AHEAD_DAYS, SCHEDULE_LOOKBACK_DAYS, SEASON_SLUG, SHARE_DISCLAIMER, SHARE_HASHTAG, STANDINGS_SHAPE, SUPPORTED, type ScheduleAheadResult, type ScheduleEntry, type SeasonInfo, type SelectedCompetition, type Selection, type ShareBracketInput, type ShareBracketOptions, type ShareCardContext, type ShareCardMarket, type ShareCardView, type ShareSnippetInput, type ShareSnippetOptions, type ShareStyle, type ShareTableInput, type SlotRef, type SmallFile, type Stage, type StandingRow, type StandingsResult, type Status, TABLE_KEY_ARG, TEAM_KIND, type TableData, type TableShareCard, type Team, type TeamInfo, type TeamKind, type TeamLookup, type UserConfig, type UserConfigRead, type VerdictExtras, type VerdictSource, allFixtures, allTeams, ambiguous, asFlavorLevel, attachFetchMeta, bounded, bracketCapability, bracketShareCard, buildBracketTopology, buildBracketView, buildMarketSignal, bundleApplies, bundleSeasonYear, byKickoff, cacheDirFor, cacheableKeys, capabilitiesOf, competitionBase, competitionKind, competitionLabel, competitionValue, computeStandings, configPath, countdown, countdownPhrase, currentOrNextFixtureForTeam, dateNoneRead, dateNoneReadSentence, dateShareCard, dateUnreached, dateUnreachedSentence, dayAttribution, definitiveNone, deriveFavorite, deriveTables, disclaimerLine, displayWidth, emptyBatch, entryOf, favoriteStrength, fetchMeta, fixturesByDate, fixturesByGroup, fixturesByTeam, fixturesInLiveWindow, flagEmoji, flavorsFor, formatBracketCompactLine, formatBracketList, formatBracketMatchLine, formatBracketTree, formatDate, formatKickoff, formatShareBracket, formatShareSnippet, formatShareTable, formatTime, getBracket, getKnockoutFixtures, getLiveMatches, getLiveRead, getMarketSignal, getMarketSignals, getMatchById, getMatchesForDate, getNextFixtureForTeam, getScheduleAhead, getStandings, groups, hasLiveWindow, hasSaneDistribution, humanLabel, isCacheable, isFinished, isFlavorLevel, isHumanLabel, isKnockoutStage, isKnownSource, isLive, isMexicoNationalTeam, isPinnedSide, isPlaceholderSide, isReliableMarketSignal, isStaleSignal, isTeam, isTournamentWindowOver, isUpcoming, isValidDate, isValidTimeZone, joinSegments, knockoutWindow, listCompetitions, liveNoneRead, liveNoneReadSentence, liveShareCard, liveSourceLabel, liveWindowMsFor, loadBracketTopology, localDate, lookAtEntry, lookAtOwnFile, lookAtSmallFile, lookupTeam, makeAdapter, makeMarketProvider, malformed, mapEspnEvent, mapsCleanly, marketAttributionText, marketBlock, marketDisplayable, marketFavoriteText, marketFixtureForTeam, marketLine, marketProbabilityText, marketRelevant, marketScopeVerdict, marketSignalRendersFor, marketSourceLabel, marketsCoverCompetition, marketsNoneRead, marketsNoneReadNote, marketsNoneReadSentence, matchFlair, matchFlairs, matchFlavor, matchKey, matchLocation, matchNoneReadSentence, matchShareCard, matchWindowSentence, mergeLive, modeLine, nationArg, nationToFlag, nationToRegion, nextFixtureForPin, nextFixtureForTeam, nextHorizonSentence, nextNoneReadSentence, nextShareCard, normalizeLang, normalizeOutcomes, outcomeFromScore, padVisible, parseCachedMarketSignal, parseCachedMatch, parseCachedMatches, parseCachedScheduleIndex, parseStandings, parseTeamSlot, parsedValue, pinUnder, productFlag, rallyCryFor, rallyEntryFor, readJsonBounded, readSmallFile, readUserConfig, resolveClub, resolveCompetition, resolveMarketSource, resolveTz, resolvedValues, retryAfterMs, rosterFor, sanitizeBundledFixture, savedSlug, scheduleEntryOf, scheduleSpan, scoreline, sealMarketSignal, sealMatch, sealScheduleEntry, sealSeason, selectOne, selectedCompetition, selectionExtras, selectionRefusal, selectionVerdict, servedExtras, stageLabel, stageLabelI18n, standingsShapeOf, statesPartial, t, tableData, tableKeyArg, tableShareCard, tableTitle, teamKind, truncateVisible, unresolved, unservedSentence, valid, verdictExtras, verdictNotice, verdictQualifiers, withFlag };
4070
+ export { type AmbientMatch, type AmbientView, BRACKET_STAGE_ORDER, BUNDLE_COMPETITION, type BatchResolution, type BetweenEditions, type BoundedList, type BracketFormatOpts, type BracketMatchNode, type BracketMatchView, type BracketResult, type BracketShareCard, type BracketTopology, type BracketView, type BuildSignalInput, COMPETITION_KIND, COUNTDOWN_NOW, type Capabilities, type Capability, type ChosenBy, type ClubResolution, type CompetitionEntry, type CompetitionKey, type CompetitionKind, type CompetitionSelection, DEFAULT_COMPETITION, DEFAULT_COOLDOWN_MS, DEFAULT_FLAVOR, DEFAULT_MAX_AGE_MS, DISCLAIMER, type DayAttribution, type DerivedTables, EARLIER_RECORD_NOTE, EspnAdapter, type EspnAdapterOptions, FAN_PROJECT, FLAVOR_BANKS, FLAVOR_LEVELS, FLAVOR_MOMENTS, FakeMarketProvider, type FakeMarketProviderOptions, type FavoriteStrength, type FetchMeta, type FetchWindowOptions, type FileEntry, type Flair, type FlairOpts, type FlavorBank, type FlavorLevel, type Moment as FlavorMoment, type FlavorOpts, type FormatOpts, type GroupStandings, KNOCKOUT_EXTRA_TIME_MS, KNOWN_SOURCES, type KnockoutFixturesResult, LIVE_WINDOW_MS, type Lang, type LedgerRow, type ListedCompetition, type LiveReadResult, type LiveResult, MARKETS_SCOPE_NOTE, MARKET_COMPETITIONS, MAX_CONFIG_BYTES, MAX_COOLDOWN_MS, MAX_LABEL_COLUMNS, MAX_RESPONSE_BYTES, MAX_SCHEDULE_INDEX, type MapContext, type MarketFavorite, type MarketMapping, type MarketMappingTable, type MarketOutcome, type MarketOutcomeKind, type MarketProvider, type MarketSignal, type MarketSignalOptions, type MarketSignalsResult, type Match, type MatchByIdResult, type MatchEvent, type MatchShareCard, NO_BRACKET, type NextFixtureResult, type NoConfigReason, type Outcome, type OwnFile, type ParseResult, type Pin, PolymarketProvider, type PolymarketProviderOptions, type ProviderAdapter, type ProviderCapabilities, ProviderError, type ProviderErrorKind, type PunditPick, RALLY_CRIES, type RallyCry, type RallyPin, type ReadAccount, type ResolvedParticipant, ResponseTooLargeError, type Roster, SCHEDULE_AHEAD_DAYS, SCHEDULE_LOOKBACK_DAYS, SEASON_SLUG, SHARE_DISCLAIMER, SHARE_HASHTAG, STANDINGS_SHAPE, SUPPORTED, type ScheduleAheadResult, type ScheduleEntry, type SeasonInfo, type SelectedCompetition, type Selection, type ShareBracketInput, type ShareBracketOptions, type ShareCardContext, type ShareCardMarket, type ShareCardView, type ShareSnippetInput, type ShareSnippetOptions, type ShareStyle, type ShareTableInput, type SlotRef, type SmallFile, type Stage, type StandingRow, type StandingsResult, type Status, TABLE_KEY_ARG, TEAM_KIND, type TableData, type TableShareCard, type Team, type TeamInfo, type TeamKind, type TeamLookup, type UserConfig, type UserConfigRead, type VerdictExtras, type VerdictSource, allFixtures, allTeams, ambiguous, asFlavorLevel, attachFetchMeta, bounded, bracketCapability, bracketShareCard, buildBracketTopology, buildBracketView, buildMarketSignal, bundleApplies, bundleSeasonYear, byKickoff, cacheDirFor, cacheableKeys, capabilitiesOf, competitionBase, competitionKind, competitionLabel, competitionValue, computeStandings, configPath, countdown, countdownPhrase, currentOrNextFixtureForTeam, dateNoneRead, dateNoneReadSentence, dateShareCard, dateUnreached, dateUnreachedSentence, dayAttribution, definitiveNone, deriveFavorite, deriveTables, disclaimerLine, displayWidth, emptyBatch, entryOf, favoriteStrength, fetchMeta, fixturesByDate, fixturesByGroup, fixturesByTeam, fixturesInLiveWindow, flagEmoji, flavorsFor, formatBracketCompactLine, formatBracketList, formatBracketMatchLine, formatBracketTree, formatDate, formatKickoff, formatShareBracket, formatShareSnippet, formatShareTable, formatTime, getBracket, getKnockoutFixtures, getLiveMatches, getLiveRead, getMarketSignal, getMarketSignals, getMatchById, getMatchesForDate, getNextFixtureForTeam, getScheduleAhead, getStandings, groups, hasLiveWindow, hasSaneDistribution, humanLabel, isCacheable, isFinished, isFlavorLevel, isHumanLabel, isKnockoutStage, isKnownSource, isLive, isMexicoNationalTeam, isPinnedSide, isPlaceholderSide, isReliableMarketSignal, isStaleSignal, isTeam, isTournamentWindowOver, isUpcoming, isValidDate, isValidTimeZone, joinSegments, knockoutWindow, listCompetitions, liveNoneRead, liveNoneReadSentence, liveShareCard, liveSourceLabel, liveWindowMsFor, loadBracketTopology, localDate, lookAtEntry, lookAtOwnFile, lookAtSmallFile, lookupTeam, makeAdapter, makeMarketProvider, malformed, mapEspnEvent, mapsCleanly, marketAttributionText, marketBlock, marketDisplayable, marketFavoriteText, marketFixtureForTeam, marketLine, marketProbabilityText, marketRelevant, marketScopeVerdict, marketSignalRendersFor, marketSourceLabel, marketsCoverCompetition, marketsNoneRead, marketsNoneReadNote, marketsNoneReadSentence, matchFlair, matchFlairs, matchFlavor, matchKey, matchLocation, matchNoneReadSentence, matchShareCard, matchWindowSentence, mergeLive, modeLine, nationArg, nationToFlag, nationToRegion, nextFixtureForPin, nextFixtureForTeam, nextHorizonSentence, nextNoneReadSentence, nextShareCard, normalizeLang, normalizeOutcomes, outcomeFromScore, padVisible, parseCachedMarketSignal, parseCachedMatch, parseCachedMatches, parseCachedScheduleIndex, parseStandings, parseTeamSlot, parsedValue, pinUnder, productFlag, rallyCryFor, rallyEntryFor, readJsonBounded, readSmallFile, readUserConfig, requestClock, resolveClub, resolveCompetition, resolveMarketSource, resolveTz, resolvedValues, retryAfterMs, rosterFor, sanitizeBundledFixture, savedSlug, scheduleEntryOf, scheduleSpan, scoreline, sealMarketSignal, sealMatch, sealScheduleEntry, sealSeason, selectOne, selectedCompetition, selectionExtras, selectionRefusal, selectionVerdict, servedExtras, stageLabel, stageLabelI18n, standingsShapeOf, statesPartial, t, tableData, tableKeyArg, tableShareCard, tableTitle, teamKind, truncateVisible, unresolved, unservedSentence, valid, verdictExtras, verdictNotice, verdictQualifiers, withFlag };
package/dist/index.js CHANGED
@@ -763,7 +763,7 @@ function formatTime(iso, opts = {}) {
763
763
  }).format(when);
764
764
  }
765
765
  var COUNTDOWN_NOW = "now";
766
- function countdown(iso, from = /* @__PURE__ */ new Date()) {
766
+ function countdown(iso, from) {
767
767
  const when = parsedDate(iso);
768
768
  if (!when) return UNKNOWN_TIME;
769
769
  const ms = when.getTime() - from.getTime();
@@ -776,7 +776,7 @@ function countdown(iso, from = /* @__PURE__ */ new Date()) {
776
776
  if (hours > 0) return `${hours}h${mins}m`;
777
777
  return `${mins}m`;
778
778
  }
779
- function countdownPhrase(lang, iso, from = /* @__PURE__ */ new Date()) {
779
+ function countdownPhrase(lang, iso, from) {
780
780
  const left = countdown(iso, from);
781
781
  return left === COUNTDOWN_NOW ? t(lang, "countdown.now") : t(lang, "next.in", { countdown: left });
782
782
  }
@@ -4890,7 +4890,7 @@ function fetchMeta(result) {
4890
4890
  // src/adapters/espn.ts
4891
4891
  var ESPN_SOCCER = "https://site.api.espn.com/apis/site/v2/sports/soccer";
4892
4892
  var DEFAULT_COMPETITION = BUNDLED_SLUG;
4893
- var USER_AGENT = `claudinho/${"0.11.1"} (+https://github.com/arturogarrido/claudinho)`;
4893
+ var USER_AGENT = `claudinho/${"0.11.2"} (+https://github.com/arturogarrido/claudinho)`;
4894
4894
  var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
4895
4895
  var DEFAULT_COOLDOWN_MS = 5 * 6e4;
4896
4896
  var MAX_COOLDOWN_MS = 15 * 6e4;
@@ -6606,6 +6606,9 @@ function mergeLive(base, live) {
6606
6606
  function clockOf(adapter) {
6607
6607
  return new Date(adapter.now?.() ?? Date.now());
6608
6608
  }
6609
+ function requestClock(adapter, given) {
6610
+ return given ?? clockOf(adapter);
6611
+ }
6609
6612
  function providerDayOf(adapter, instant) {
6610
6613
  if (Number.isNaN(instant.getTime())) return "";
6611
6614
  return adapter.bucketDay?.(instant) || instant.toISOString().slice(0, 10);
@@ -6780,7 +6783,7 @@ async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Dat
6780
6783
  });
6781
6784
  let refreshRead = {};
6782
6785
  if (candidate) {
6783
- const { partial, served: _served, ...r } = await getMatchById(adapter, candidate.id);
6786
+ const { partial, served: _served, ...r } = await getMatchById(adapter, candidate.id, now);
6784
6787
  refreshRead = partial ? { partial } : {};
6785
6788
  const m = r.degraded ? candidate : r.match ?? candidate;
6786
6789
  if (!isFinished(m.status)) return { ...r, match: m, ...bothReads(windowRead, refreshRead) };
@@ -8859,6 +8862,7 @@ export {
8859
8862
  readJsonBounded,
8860
8863
  readSmallFile,
8861
8864
  readUserConfig,
8865
+ requestClock,
8862
8866
  resolveClub,
8863
8867
  resolveCompetition,
8864
8868
  resolveMarketSource,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudinho/core",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "description": "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. Independent fan project. Not affiliated with FIFA, any confederation, league or club, or Anthropic.",
5
5
  "type": "module",
6
6
  "license": "MIT",