@claudinho/cli 0.9.3 → 0.9.4

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.
Files changed (3) hide show
  1. package/README.md +11 -3
  2. package/dist/index.js +1443 -513
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -154,10 +154,11 @@ var ALIASES = [
154
154
  ["Burma", "MM"],
155
155
  ["Cape Verde", "CV"]
156
156
  ];
157
- var BY_NATION = Object.fromEntries(
158
- [...NATIONS, ...ALIASES].map(([name, code]) => [norm(name), code])
157
+ var BY_NATION = Object.assign(
158
+ /* @__PURE__ */ Object.create(null),
159
+ Object.fromEntries([...NATIONS, ...ALIASES].map(([name, code]) => [norm(name), code]))
159
160
  );
160
- var BY_CODE = {
161
+ var BY_CODE = Object.assign(/* @__PURE__ */ Object.create(null), {
161
162
  MEX: "MX",
162
163
  RSA: "ZA",
163
164
  KOR: "KR",
@@ -218,7 +219,7 @@ var BY_CODE = {
218
219
  HON: "HN",
219
220
  COD: "CD",
220
221
  MLI: "ML"
221
- };
222
+ });
222
223
  function nationToFlag(nameOrCode) {
223
224
  const region = nationToRegion(nameOrCode);
224
225
  return region ? flagEmoji(region) : NEUTRAL;
@@ -226,7 +227,7 @@ function nationToFlag(nameOrCode) {
226
227
  var INTL_BY_NAME;
227
228
  function intlNameMap() {
228
229
  if (INTL_BY_NAME) return INTL_BY_NAME;
229
- const map = {};
230
+ const map = /* @__PURE__ */ Object.create(null);
230
231
  try {
231
232
  const dn = new Intl.DisplayNames(["en"], { type: "region" });
232
233
  for (let a = 65; a <= 90; a++) {
@@ -251,11 +252,8 @@ function intlNameMap() {
251
252
  }
252
253
  function nationToRegion(nameOrCode) {
253
254
  if (!nameOrCode) return void 0;
254
- const byName = BY_NATION[norm(nameOrCode)];
255
- if (byName) return byName;
256
- const byCode = BY_CODE[nameOrCode.trim().toUpperCase()];
257
- if (byCode) return byCode;
258
- return intlNameMap()[norm(nameOrCode)];
255
+ const str = (v) => typeof v === "string" && v ? v : void 0;
256
+ return str(BY_NATION[norm(nameOrCode)]) ?? str(BY_CODE[nameOrCode.trim().toUpperCase()]) ?? str(intlNameMap()[norm(nameOrCode)]);
259
257
  }
260
258
  var EN = {
261
259
  "bracket.title": "Knockout bracket",
@@ -275,6 +273,7 @@ var EN = {
275
273
  "bracket.slot.loser": "{stage} {n} loser",
276
274
  "bracket.slot.tbd": "TBD",
277
275
  "live.data": "Live data: {source}",
276
+ "standings.unavailable": "Live standings unavailable.",
278
277
  "share.tryIt": "Try it: {line}",
279
278
  "stage.group": "Group {group}",
280
279
  "stage.groupStage": "Group stage",
@@ -304,6 +303,7 @@ var ES = {
304
303
  "bracket.slot.loser": "Perdedor {stage} {n}",
305
304
  "bracket.slot.tbd": "Por definir",
306
305
  "live.data": "Datos en vivo: {source}",
306
+ "standings.unavailable": "Tabla en vivo no disponible.",
307
307
  "share.tryIt": "Pru\xE9balo: {line}",
308
308
  "stage.group": "Grupo {group}",
309
309
  "stage.groupStage": "Fase de grupos",
@@ -333,6 +333,7 @@ var PT = {
333
333
  "bracket.slot.loser": "Perdedor {stage} {n}",
334
334
  "bracket.slot.tbd": "A definir",
335
335
  "live.data": "Dados ao vivo: {source}",
336
+ "standings.unavailable": "Classifica\xE7\xE3o ao vivo indispon\xEDvel.",
336
337
  "share.tryIt": "Experimente: {line}",
337
338
  "stage.group": "Grupo {group}",
338
339
  "stage.groupStage": "Fase de grupos",
@@ -362,6 +363,7 @@ var FR = {
362
363
  "bracket.slot.loser": "Perdant {stage} {n}",
363
364
  "bracket.slot.tbd": "\xC0 d\xE9finir",
364
365
  "live.data": "Donn\xE9es en direct : {source}",
366
+ "standings.unavailable": "Classement en direct indisponible.",
365
367
  "share.tryIt": "Essayez : {line}",
366
368
  "stage.group": "Groupe {group}",
367
369
  "stage.groupStage": "Phase de groupes",
@@ -446,7 +448,14 @@ function safeLocale(locale) {
446
448
  return "en";
447
449
  }
448
450
  }
451
+ function parsedDate(iso) {
452
+ const t2 = Date.parse(iso);
453
+ return Number.isFinite(t2) ? new Date(t2) : void 0;
454
+ }
455
+ var UNKNOWN_TIME = "\u2014";
449
456
  function formatKickoff(iso, opts = {}) {
457
+ const when = parsedDate(iso);
458
+ if (!when) return UNKNOWN_TIME;
450
459
  const tz = resolveTz(opts.tz);
451
460
  const locale = safeLocale(opts.locale);
452
461
  return new Intl.DateTimeFormat(locale, {
@@ -456,18 +465,22 @@ function formatKickoff(iso, opts = {}) {
456
465
  minute: "2-digit",
457
466
  hour12: false,
458
467
  timeZone: tz
459
- }).format(new Date(iso));
468
+ }).format(when);
460
469
  }
461
470
  function formatDate(iso, opts = {}) {
471
+ const when = parsedDate(iso);
472
+ if (!when) return UNKNOWN_TIME;
462
473
  const tz = resolveTz(opts.tz);
463
474
  const locale = safeLocale(opts.locale);
464
475
  return new Intl.DateTimeFormat(locale, {
465
476
  month: "short",
466
477
  day: "numeric",
467
478
  timeZone: tz
468
- }).format(new Date(iso));
479
+ }).format(when);
469
480
  }
470
481
  function formatTime(iso, opts = {}) {
482
+ const when = parsedDate(iso);
483
+ if (!when) return UNKNOWN_TIME;
471
484
  const tz = resolveTz(opts.tz);
472
485
  const locale = safeLocale(opts.locale);
473
486
  return new Intl.DateTimeFormat(locale, {
@@ -475,10 +488,12 @@ function formatTime(iso, opts = {}) {
475
488
  minute: "2-digit",
476
489
  hour12: false,
477
490
  timeZone: tz
478
- }).format(new Date(iso));
491
+ }).format(when);
479
492
  }
480
493
  function countdown(iso, from = /* @__PURE__ */ new Date()) {
481
- const ms = new Date(iso).getTime() - from.getTime();
494
+ const when = parsedDate(iso);
495
+ if (!when) return UNKNOWN_TIME;
496
+ const ms = when.getTime() - from.getTime();
482
497
  if (ms <= 0) return "now";
483
498
  const totalMin = Math.floor(ms / 6e4);
484
499
  const days = Math.floor(totalMin / 1440);
@@ -489,71 +504,56 @@ function countdown(iso, from = /* @__PURE__ */ new Date()) {
489
504
  return `${mins}m`;
490
505
  }
491
506
  function localDate(iso, tz) {
507
+ const when = parsedDate(iso);
508
+ if (!when) return "";
492
509
  const zone = resolveTz(tz);
493
510
  return new Intl.DateTimeFormat("en-CA", {
494
511
  year: "numeric",
495
512
  month: "2-digit",
496
513
  day: "2-digit",
497
514
  timeZone: zone
498
- }).format(new Date(iso));
515
+ }).format(when);
499
516
  }
500
517
  function shiftUtcDate(dateISO, days) {
501
518
  const [y, m, d] = dateISO.slice(0, 10).split("-").map(Number);
502
519
  return new Date(Date.UTC(y ?? 1970, (m ?? 1) - 1, (d ?? 1) + days)).toISOString().slice(0, 10);
503
520
  }
504
- var FEED_TEXT_MAX = 100;
505
- function sanitizeFeedText(value, max = FEED_TEXT_MAX) {
506
- let out2 = "";
507
- let count = 0;
508
- for (const ch of String(value)) {
509
- const cp = ch.codePointAt(0) ?? 0;
510
- const isWhitespaceControl = cp === 9 || cp === 10 || cp === 13;
511
- if ((cp <= 31 || cp >= 127 && cp <= 159) && !isWhitespaceControl) continue;
512
- if (count >= max) break;
513
- out2 += isWhitespaceControl ? " " : ch;
514
- count++;
515
- }
516
- return out2;
517
- }
518
- function sanitizeTeam(t2) {
519
- return {
520
- ...t2 ?? {},
521
- code: sanitizeFeedText(t2?.code ?? ""),
522
- name: sanitizeFeedText(t2?.name ?? ""),
523
- flag: sanitizeFeedText(t2?.flag ?? "")
524
- };
525
- }
526
- function finiteOrUndefined(v) {
527
- return typeof v === "number" && Number.isFinite(v) ? v : void 0;
528
- }
529
- function sanitizeScorePair(v) {
530
- const home = finiteOrUndefined(v?.home);
531
- const away = finiteOrUndefined(v?.away);
532
- return home !== void 0 && away !== void 0 ? { home, away } : void 0;
533
- }
534
- function sanitizeMatchStrings(m) {
535
- const score = sanitizeScorePair(m.score);
536
- return {
537
- ...m,
538
- venue: sanitizeFeedText(m.venue ?? ""),
539
- city: m.city == null ? m.city : sanitizeFeedText(m.city),
540
- country: m.country == null ? m.country : sanitizeFeedText(m.country),
541
- home: sanitizeTeam(m.home),
542
- away: sanitizeTeam(m.away),
543
- score,
544
- shootout: score ? sanitizeScorePair(m.shootout) : void 0,
545
- minute: finiteOrUndefined(m.minute)
546
- };
547
- }
548
521
  var segmenter = new Intl.Segmenter();
549
522
  var WIDE_CLUSTER = new RegExp("^(?:\\p{Regional_Indicator}|\\p{Extended_Pictographic})", "u");
523
+ var WIDE_BASE = /[ᄀ-ᅟ⺀-〾ぁ-㏿㐀-䶿一-鿿ꀀ-꓏ꥠ-꥿가-힣豈-﫿︐-︙︰-﹯＀-⦆¢-₩\u{20000}-\u{2FFFD}\u{30000}-\u{3FFFD}]/u;
524
+ var KEYCAP = /\u{20E3}/u;
525
+ var ZERO_WIDTH_BASE = /[\p{Mn}\p{Me}\p{Cf}\p{Cc}]/u;
526
+ function clusterWidth(segment) {
527
+ if (WIDE_CLUSTER.test(segment) || KEYCAP.test(segment)) return 2;
528
+ const first = segment.codePointAt(0);
529
+ if (first === void 0) return 0;
530
+ const base = String.fromCodePoint(first);
531
+ if (ZERO_WIDTH_BASE.test(base)) return 0;
532
+ return WIDE_BASE.test(base) ? 2 : 1;
533
+ }
550
534
  function displayWidth(s) {
551
535
  let w = 0;
552
536
  for (const { segment } of segmenter.segment(s)) {
553
- w += WIDE_CLUSTER.test(segment) ? 2 : 1;
537
+ w += clusterWidth(segment);
554
538
  }
555
539
  return w;
556
540
  }
541
+ function truncateVisible(s, maxColumns, marker = "\u2026") {
542
+ if (displayWidth(s) <= maxColumns) return s;
543
+ const budget = Math.max(0, maxColumns - displayWidth(marker));
544
+ let out2 = "";
545
+ let w = 0;
546
+ for (const { segment } of segmenter.segment(s)) {
547
+ const cw = clusterWidth(segment);
548
+ if (w + cw > budget) break;
549
+ out2 += segment;
550
+ w += cw;
551
+ }
552
+ return out2 + marker;
553
+ }
554
+ function* graphemes(s) {
555
+ for (const { segment } of segmenter.segment(s)) yield segment;
556
+ }
557
557
  function padVisible(s, width) {
558
558
  const w = displayWidth(s);
559
559
  return w >= width ? s : s + " ".repeat(width - w);
@@ -2953,47 +2953,285 @@ function rosterAtZero(matches) {
2953
2953
  }
2954
2954
  return [...teams.values()].sort((a, b) => a.name.localeCompare(b.name)).map(blankRow);
2955
2955
  }
2956
- var ESPN_SOCCER = "https://site.api.espn.com/apis/site/v2/sports/soccer";
2957
- var DEFAULT_COMPETITION = "fifa.world";
2958
- var DEFAULT_BASE = `${ESPN_SOCCER}/${DEFAULT_COMPETITION}`;
2959
- var USER_AGENT = `claudinho/${"0.9.3"} (+https://github.com/arturogarrido/claudinho)`;
2960
- var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
2961
- function competitionBase(slug) {
2962
- return `${ESPN_SOCCER}/${slug}`;
2956
+ function takeBounded(value, max) {
2957
+ if (!Array.isArray(value)) return [];
2958
+ return value.length > max ? value.slice(0, max) : value;
2963
2959
  }
2964
- var DEFAULT_TIMEOUT_MS = 6e3;
2965
- var STANDINGS_SHARE_MS = 3e4;
2966
- var ProviderError = class extends Error {
2967
- kind;
2968
- status;
2969
- constructor(message, kind, status) {
2970
- super(message);
2971
- this.name = "ProviderError";
2972
- this.kind = kind;
2973
- this.status = status;
2960
+ var valid = (value) => ({ kind: "valid", value });
2961
+ var definitiveNone = (reason) => ({
2962
+ kind: "definitive-none",
2963
+ reason
2964
+ });
2965
+ var malformed = (reason) => ({ kind: "malformed", reason });
2966
+ var ambiguous = (reason) => ({ kind: "ambiguous", reason });
2967
+ var unresolved = (reason) => ({ kind: "unresolved", reason });
2968
+ function parsedValue(r) {
2969
+ return r.kind === "valid" ? r.value : void 0;
2970
+ }
2971
+ function isCacheable(r) {
2972
+ return r.kind === "valid" || r.kind === "definitive-none" || r.kind === "ambiguous";
2973
+ }
2974
+ var FORBIDDEN_IN_LABEL = new RegExp("[\\p{Cc}\\p{Cf}\\p{Zl}\\p{Zp}\\p{Cs}\\p{Co}\\p{Cn}]|\\p{Default_Ignorable_Code_Point}", "u");
2975
+ var EMOJI_IN_LABEL = new RegExp("\\p{Extended_Pictographic}|\\p{Regional_Indicator}|\\p{Emoji_Modifier}|\\u{20E3}", "u");
2976
+ var MAX_LABEL_INPUT_UNITS = 4096;
2977
+ var MAX_LABEL_COLUMNS = 100;
2978
+ function humanLabel(value, maxColumns = MAX_LABEL_COLUMNS) {
2979
+ if (typeof value !== "string" || value === "") return "";
2980
+ const capped = value.length > MAX_LABEL_INPUT_UNITS ? value.slice(0, MAX_LABEL_INPUT_UNITS) : value;
2981
+ return visible(runToFixedPoint(capped, maxColumns));
2982
+ }
2983
+ function visible(label) {
2984
+ return label !== "" && displayWidth(label) === 0 ? "" : label;
2985
+ }
2986
+ function runToFixedPoint(capped, maxColumns) {
2987
+ const first = sealLabelOnce(capped, maxColumns);
2988
+ if (!lastPassDropped) return first;
2989
+ let out2 = first;
2990
+ for (let pass = 0; pass < 3; pass++) {
2991
+ const again = sealLabelOnce(out2, maxColumns);
2992
+ if (again === out2) return out2;
2993
+ out2 = again;
2994
+ }
2995
+ return sealLabelOnce(out2, maxColumns) === out2 ? out2 : "";
2996
+ }
2997
+ var lastPassDropped = false;
2998
+ function sealLabelOnce(value, maxColumns) {
2999
+ lastPassDropped = false;
3000
+ let normalized;
3001
+ try {
3002
+ normalized = value.normalize("NFC");
3003
+ } catch {
3004
+ return "";
2974
3005
  }
2975
- /** 429/403 the upstream is refusing us; retrying at the live cadence makes it worse. */
2976
- get throttled() {
2977
- return this.kind === "http" && (this.status === 429 || this.status === 403);
3006
+ const maxCodePoints = Math.max(16, maxColumns * 4);
3007
+ let out2 = "";
3008
+ let width = 0;
3009
+ let points = 0;
3010
+ for (const cluster of graphemes(normalized)) {
3011
+ if ([...cluster].length > 8) {
3012
+ lastPassDropped = true;
3013
+ continue;
3014
+ }
3015
+ if (EMOJI_IN_LABEL.test(cluster)) {
3016
+ lastPassDropped = true;
3017
+ continue;
3018
+ }
3019
+ let piece = "";
3020
+ for (const ch of cluster) {
3021
+ const cp = ch.codePointAt(0) ?? 0;
3022
+ if (cp === 9 || cp === 10 || cp === 13) {
3023
+ piece += " ";
3024
+ continue;
3025
+ }
3026
+ if (FORBIDDEN_IN_LABEL.test(ch)) {
3027
+ lastPassDropped = true;
3028
+ continue;
3029
+ }
3030
+ piece += ch;
3031
+ }
3032
+ if (!piece) {
3033
+ lastPassDropped = true;
3034
+ continue;
3035
+ }
3036
+ const w = displayWidth(piece);
3037
+ const cps = [...piece].length;
3038
+ if (width + w > maxColumns || points + cps > maxCodePoints) break;
3039
+ out2 += piece;
3040
+ width += w;
3041
+ points += cps;
2978
3042
  }
2979
- };
3043
+ try {
3044
+ return out2.normalize("NFC").trim();
3045
+ } catch {
3046
+ return out2.trim();
3047
+ }
3048
+ }
3049
+ function opaqueId(value, grammar) {
3050
+ return typeof value === "string" && grammar.test(value) ? value : void 0;
3051
+ }
3052
+ var ESPN_ID = /^[0-9]{1,20}$/;
3053
+ var ISO_INSTANT = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:[Zz]|[+-]\d{2}(?::?\d{2})?)$/;
3054
+ var ISO_CANONICAL = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
3055
+ function calendarValid(iso) {
3056
+ const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso);
3057
+ if (!m) return false;
3058
+ const year = Number(m[1]);
3059
+ const month = Number(m[2]);
3060
+ const day = Number(m[3]);
3061
+ if (month < 1 || month > 12 || day < 1) return false;
3062
+ return day <= new Date(Date.UTC(year, month, 0)).getUTCDate();
3063
+ }
3064
+ function canonicalTimestamp(value) {
3065
+ if (typeof value !== "string" || !ISO_INSTANT.test(value)) return void 0;
3066
+ if (!calendarValid(value)) return void 0;
3067
+ const t2 = Date.parse(value);
3068
+ if (!Number.isFinite(t2)) return void 0;
3069
+ const out2 = new Date(t2).toISOString();
3070
+ return ISO_CANONICAL.test(out2) ? out2 : void 0;
3071
+ }
3072
+ function productFlag(nameOrCode) {
3073
+ return nationToFlag(nameOrCode);
3074
+ }
3075
+ function count(value, max) {
3076
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= max ? value : void 0;
3077
+ }
3078
+ function quantity(value) {
3079
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
3080
+ }
3081
+ function probability(value) {
3082
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1 ? value : void 0;
3083
+ }
3084
+ function member(value, allowed) {
3085
+ return typeof value === "string" && allowed.has(value) ? value : void 0;
3086
+ }
3087
+ function flag(value) {
3088
+ return typeof value === "boolean" ? value : void 0;
3089
+ }
3090
+ var MAX_GOALS = 99;
3091
+ var MAX_MINUTE = 200;
3092
+ var MAX_MATCH_EVENTS = 128;
3093
+ var TEAM_CODE_COLUMNS = 8;
3094
+ var STAGES = /* @__PURE__ */ new Set(["GROUP", "R32", "R16", "QF", "SF", "3P", "F", "FRIENDLY"]);
3095
+ var STATUSES = /* @__PURE__ */ new Set(["SCHEDULED", "LIVE", "HT", "FT", "POSTPONED", "CANCELLED"]);
3096
+ var EVENT_TYPES = /* @__PURE__ */ new Set(["GOAL", "OWN_GOAL", "PEN", "YELLOW", "RED", "SUB"]);
3097
+ function teamCode(raw, fallbackName) {
3098
+ const upper = typeof raw === "string" ? raw.toUpperCase() : raw;
3099
+ const stated = humanLabel(upper, TEAM_CODE_COLUMNS);
3100
+ if (stated) return stated;
3101
+ return humanLabel([...fallbackName].slice(0, 3).join("").toUpperCase(), TEAM_CODE_COLUMNS);
3102
+ }
3103
+ function sealTeam(raw) {
3104
+ if (!raw || typeof raw !== "object") return void 0;
3105
+ const t2 = raw;
3106
+ const name = humanLabel(t2.name);
3107
+ if (!name) return void 0;
3108
+ const code = teamCode(t2.code, name);
3109
+ return { code, name, flag: productFlag(name) };
3110
+ }
3111
+ function sealScorePair(raw) {
3112
+ if (!raw || typeof raw !== "object") return void 0;
3113
+ const v = raw;
3114
+ const home = count(v.home, MAX_GOALS);
3115
+ const away = count(v.away, MAX_GOALS);
3116
+ return home !== void 0 && away !== void 0 ? { home, away } : void 0;
3117
+ }
3118
+ function sealEvent(raw) {
3119
+ if (!raw || typeof raw !== "object") return void 0;
3120
+ const e = raw;
3121
+ const type = member(e.type, EVENT_TYPES);
3122
+ if (!type) return void 0;
3123
+ const minute = count(e.minute, MAX_MINUTE);
3124
+ if (minute === void 0) return void 0;
3125
+ const out2 = { type, minute, teamCode: humanLabel(e.teamCode, TEAM_CODE_COLUMNS) };
3126
+ const player = humanLabel(e.player);
3127
+ if (player) out2.player = player;
3128
+ return out2;
3129
+ }
3130
+ function sealMatch(parts, opts = {}) {
3131
+ if (!parts || typeof parts !== "object") return malformed("match is not an object");
3132
+ const id = opaqueId(parts.id, ESPN_ID);
3133
+ if (!id) return malformed("match id is absent or not an identifier");
3134
+ const kickoff = canonicalTimestamp(parts.kickoff);
3135
+ if (!kickoff) return malformed("match kickoff is absent or not one instant");
3136
+ const stage = member(parts.stage, STAGES);
3137
+ if (!stage) return malformed("match stage is not a known stage");
3138
+ const status = member(parts.status, STATUSES);
3139
+ if (!status) return malformed("match status is not a known status");
3140
+ const home = sealTeam(parts.home);
3141
+ const away = sealTeam(parts.away);
3142
+ if (!home || !away) return malformed("match does not name both teams");
3143
+ if (home.code === away.code && home.name === away.name) {
3144
+ return definitiveNone("both competitors are the same team");
3145
+ }
3146
+ const group = humanLabel(parts.group) || void 0;
3147
+ const city = humanLabel(parts.city) || void 0;
3148
+ const country = humanLabel(parts.country) || void 0;
3149
+ const score = status === "SCHEDULED" ? void 0 : sealScorePair(parts.score);
3150
+ if ((status === "LIVE" || status === "HT" || status === "FT") && !score) {
3151
+ return malformed("match claims an unreadable score");
3152
+ }
3153
+ const finished = status === "FT";
3154
+ const canGoToPenalties = stage !== "GROUP";
3155
+ const shootoutPresent = parts.shootout !== void 0 && parts.shootout !== null;
3156
+ const parsedShootout = shootoutPresent ? sealScorePair(parts.shootout) : void 0;
3157
+ const shootoutStatus = status === "LIVE" || status === "FT";
3158
+ const shootout = parsedShootout && score && canGoToPenalties && shootoutStatus && !(finished && parsedShootout.home === parsedShootout.away) ? parsedShootout : void 0;
3159
+ const claimedWinner = teamCode(parts.winnerCode, "");
3160
+ const claimedSide = claimedWinner === home.code ? "home" : claimedWinner === away.code ? "away" : void 0;
3161
+ let winnerCode;
3162
+ const unusableShootoutCouldDecide = canGoToPenalties && shootoutPresent && !shootout;
3163
+ if (claimedSide && score && finished && !unusableShootoutCouldDecide) {
3164
+ const level = score.home === score.away;
3165
+ const decider = shootout ?? score;
3166
+ if (decider && decider.home !== decider.away) {
3167
+ if ((decider.home > decider.away ? "home" : "away") === claimedSide) winnerCode = claimedWinner;
3168
+ } else if (level && !shootoutPresent && canGoToPenalties) {
3169
+ winnerCode = claimedWinner;
3170
+ }
3171
+ }
3172
+ const events = opts.events === false ? [] : takeBounded(parts.events, MAX_MATCH_EVENTS).map(sealEvent).filter((e) => !!e);
3173
+ const out2 = { id, stage };
3174
+ if (group) out2.group = group;
3175
+ out2.kickoff = kickoff;
3176
+ out2.venue = humanLabel(parts.venue);
3177
+ if (city) out2.city = city;
3178
+ if (country) out2.country = country;
3179
+ out2.home = home;
3180
+ out2.away = away;
3181
+ if (score) out2.score = score;
3182
+ if (shootout) out2.shootout = shootout;
3183
+ const minute = count(parts.minute, MAX_MINUTE);
3184
+ if (minute !== void 0) out2.minute = minute;
3185
+ out2.status = status;
3186
+ if (events.length) out2.events = events;
3187
+ if (winnerCode) out2.winnerCode = winnerCode;
3188
+ out2.updatedAt = canonicalTimestamp(parts.updatedAt) ?? "";
3189
+ return valid(out2);
3190
+ }
3191
+ function parseCachedMatch(raw, opts = {}) {
3192
+ if (!raw || typeof raw !== "object") return definitiveNone("cache entry is not an object");
3193
+ return sealMatch(raw, opts);
3194
+ }
3195
+ var MAX_EVENTS = 300;
3196
+ var MAX_GROUPS = 16;
3197
+ var MAX_GROUP_ROWS = 32;
3198
+ var STAGES2 = /* @__PURE__ */ new Set(["GROUP", "R32", "R16", "QF", "SF", "3P", "F", "FRIENDLY"]);
3199
+ function teamNames(t2) {
3200
+ return [t2?.displayName, t2?.name, t2?.location, t2?.shortDisplayName, t2?.abbreviation].map((v) => humanLabel(v)).filter((v) => v !== "");
3201
+ }
3202
+ function toParticipant(raw) {
3203
+ if (!raw || typeof raw !== "object") return malformed("competitor is not an object");
3204
+ const names = teamNames(raw.team);
3205
+ if (names.length === 0) return malformed("competitor names no team");
3206
+ const name = names[0];
3207
+ const code = teamCode(raw.team?.abbreviation, name);
3208
+ const team = { code, name, flag: productFlag(name) };
3209
+ const providerId = opaqueId(raw.team?.id, ESPN_ID);
3210
+ const known = productFlag(name) !== nationToFlag("");
3211
+ return valid(
3212
+ providerId && known ? { kind: "team", providerId, team } : { kind: "slot", team }
3213
+ );
3214
+ }
2980
3215
  function mapStatus(st) {
2981
- const name = (st?.type?.name ?? "").toUpperCase();
2982
- const state = st?.type?.state ?? "";
3216
+ const type = st?.type;
3217
+ const name = typeof type?.name === "string" ? type.name.toUpperCase() : "";
3218
+ const state = typeof type?.state === "string" ? type.state : "";
2983
3219
  if (name.includes("HALFTIME")) return "HT";
2984
3220
  if (name.includes("POSTPONED")) return "POSTPONED";
2985
3221
  if (name.includes("CANCEL")) return "CANCELLED";
2986
3222
  if (state === "pre") return "SCHEDULED";
2987
3223
  if (state === "post") return "FT";
2988
3224
  if (state === "in") return "LIVE";
2989
- return "SCHEDULED";
3225
+ return void 0;
2990
3226
  }
2991
3227
  function parseMinute(st) {
2992
- if (st?.type?.state !== "in") return void 0;
2993
- const dc = st.displayClock?.match(/(\d+)/);
2994
- if (dc) return parseInt(dc[1], 10);
2995
- if (typeof st.clock === "number" && st.clock > 0) {
2996
- return Math.floor(st.clock / 60) || void 0;
3228
+ const s = st;
3229
+ if (s?.type?.state !== "in") return void 0;
3230
+ const dc = typeof s.displayClock === "string" ? s.displayClock.match(/(\d+)/) : null;
3231
+ if (dc) return count(Number.parseInt(dc[1], 10), MAX_MINUTE);
3232
+ if (typeof s.clock === "number" && s.clock > 0) {
3233
+ const n = Math.floor(s.clock / 60);
3234
+ return n > 0 ? count(n, MAX_MINUTE) : void 0;
2997
3235
  }
2998
3236
  return void 0;
2999
3237
  }
@@ -3007,58 +3245,74 @@ var SLUG_TO_STAGE = {
3007
3245
  final: "F"
3008
3246
  };
3009
3247
  function stageFromSlug(slug) {
3010
- if (slug && SLUG_TO_STAGE[slug]) return SLUG_TO_STAGE[slug];
3011
- if (!slug) return "GROUP";
3248
+ if (slug == null || slug === "") return "GROUP";
3249
+ if (typeof slug === "string" && Object.hasOwn(SLUG_TO_STAGE, slug)) {
3250
+ const mapped = SLUG_TO_STAGE[slug];
3251
+ if (mapped) return mapped;
3252
+ }
3012
3253
  return "FRIENDLY";
3013
3254
  }
3014
- function toInt(s) {
3015
- if (s == null || s === "") return void 0;
3016
- const n = parseInt(String(s), 10);
3017
- return Number.isFinite(n) ? n : void 0;
3018
- }
3019
- function toTeam(t2) {
3020
- const name = sanitizeFeedText(
3021
- t2?.displayName ?? t2?.name ?? t2?.location ?? t2?.shortDisplayName ?? "TBD"
3022
- );
3023
- const code = sanitizeFeedText(t2?.abbreviation ?? name.slice(0, 3)).toUpperCase();
3024
- return {
3025
- code,
3026
- name,
3027
- flag: nationToFlag(sanitizeFeedText(t2?.displayName ?? t2?.abbreviation ?? name))
3028
- };
3029
- }
3030
- function mapEspnEvent(ev, ctx = {}) {
3031
- const comp = ev.competitions?.[0];
3032
- const competitors = comp?.competitors ?? [];
3033
- const homeC = competitors.find((c) => c.homeAway === "home") ?? competitors[0];
3034
- const awayC = competitors.find((c) => c.homeAway === "away") ?? competitors[1];
3255
+ function toGoals(v) {
3256
+ if (typeof v === "number") return count(v, MAX_GOALS);
3257
+ if (typeof v !== "string" || v.trim() === "") return void 0;
3258
+ return /^-?\d{1,9}$/.test(v.trim()) ? count(Number(v.trim()), MAX_GOALS) : void 0;
3259
+ }
3260
+ function parseEspnEvent(raw, ctx = {}) {
3261
+ if (!raw || typeof raw !== "object") return malformed("event is not an object");
3262
+ const ev = raw;
3263
+ const id = opaqueId(ev.id, ESPN_ID);
3264
+ if (!id) return malformed("event id is absent or not an identifier");
3265
+ const kickoff = canonicalTimestamp(ev.date);
3266
+ if (!kickoff) return malformed("event date is absent or not one instant");
3267
+ const comp = Array.isArray(ev.competitions) ? ev.competitions[0] : void 0;
3268
+ const rawCompetitors = takeBounded(comp?.competitors, 4);
3269
+ if (rawCompetitors.length !== 2) {
3270
+ return definitiveNone(`expected 2 competitors, found ${rawCompetitors.length}`);
3271
+ }
3272
+ const homeRaw = rawCompetitors.find((c) => c?.homeAway === "home");
3273
+ const awayRaw = rawCompetitors.find((c) => c?.homeAway === "away");
3274
+ if (!homeRaw || !awayRaw) return definitiveNone("competitors do not state home and away");
3275
+ const homeP = toParticipant(homeRaw);
3276
+ const awayP = toParticipant(awayRaw);
3277
+ if (homeP.kind !== "valid") return homeP;
3278
+ if (awayP.kind !== "valid") return awayP;
3279
+ const h = homeP.value;
3280
+ const a = awayP.value;
3281
+ if (h.kind === "team" && a.kind === "team" && h.providerId === a.providerId) {
3282
+ return definitiveNone("both competitors are the same team");
3283
+ }
3284
+ const home = homeP.value.team;
3285
+ const away = awayP.value.team;
3035
3286
  const status = mapStatus(ev.status ?? comp?.status);
3036
- const stage = stageFromSlug(ev.season?.slug);
3037
- const home = toTeam(homeC?.team);
3038
- const away = toTeam(awayC?.team);
3287
+ if (!status) return malformed("event status is not a status we recognize");
3288
+ const stage = member(stageFromSlug(ev.season?.slug), STAGES2) ?? "FRIENDLY";
3039
3289
  let group;
3040
3290
  if (stage === "GROUP" && ctx.groupByTeam) {
3041
3291
  group = ctx.groupByTeam[home.code] ?? ctx.groupByTeam[away.code];
3042
3292
  }
3043
- const hs = toInt(homeC?.score);
3044
- const as = toInt(awayC?.score);
3045
- const hasScore = status !== "SCHEDULED" && hs !== void 0 && as !== void 0;
3293
+ const hs = toGoals(homeRaw.score);
3294
+ const as = toGoals(awayRaw.score);
3295
+ const scoreExpected = status === "LIVE" || status === "HT" || status === "FT";
3296
+ const hasScore = scoreExpected && hs !== void 0 && as !== void 0;
3297
+ if (scoreExpected && !hasScore) return malformed("event score is absent or unreadable");
3298
+ const hShoot = toGoals(homeRaw.shootoutScore);
3299
+ const aShoot = toGoals(awayRaw.shootoutScore);
3300
+ const shootoutPresent = homeRaw.shootoutScore !== void 0 || awayRaw.shootoutScore !== void 0;
3301
+ const shootout = shootoutPresent ? { home: hShoot, away: aShoot } : void 0;
3046
3302
  let winnerCode;
3047
3303
  if (isFinished(status)) {
3048
- if (homeC?.winner) winnerCode = home.code;
3049
- else if (awayC?.winner) winnerCode = away.code;
3304
+ const winners = [homeRaw, awayRaw].filter((c) => flag(c.winner) === true);
3305
+ if (winners.length === 1) winnerCode = winners[0] === homeRaw ? home.code : away.code;
3050
3306
  }
3051
- const hShoot = toInt(homeC?.shootoutScore);
3052
- const aShoot = toInt(awayC?.shootoutScore);
3053
- const shootout = hasScore && hShoot !== void 0 && aShoot !== void 0 ? { home: hShoot, away: aShoot } : void 0;
3054
- return {
3055
- id: ev.id,
3307
+ const venue = comp?.venue;
3308
+ return sealMatch({
3309
+ id,
3056
3310
  stage,
3057
3311
  group,
3058
- kickoff: ev.date,
3059
- venue: sanitizeFeedText(comp?.venue?.fullName ?? ""),
3060
- city: sanitizeFeedText(comp?.venue?.address?.city ?? "") || void 0,
3061
- country: sanitizeFeedText(comp?.venue?.address?.country ?? "") || void 0,
3312
+ kickoff,
3313
+ venue: venue?.fullName,
3314
+ city: venue?.address?.city,
3315
+ country: venue?.address?.country,
3062
3316
  home,
3063
3317
  away,
3064
3318
  score: hasScore ? { home: hs, away: as } : void 0,
@@ -3066,57 +3320,221 @@ function mapEspnEvent(ev, ctx = {}) {
3066
3320
  minute: parseMinute(ev.status ?? comp?.status),
3067
3321
  status,
3068
3322
  winnerCode,
3069
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3070
- };
3323
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3324
+ events: ev.events
3325
+ });
3071
3326
  }
3072
- function toEspnDate(d) {
3073
- return d.replace(/\D/g, "").slice(0, 8);
3327
+ function parseEspnEvents(raw, ctx = {}) {
3328
+ const all = raw?.events;
3329
+ const readable = Array.isArray(all);
3330
+ const total = readable ? all.length : 0;
3331
+ const considered = takeBounded(all, MAX_EVENTS);
3332
+ const items = [];
3333
+ const seenIds = /* @__PURE__ */ new Set();
3334
+ let complete = readable && considered.length === total;
3335
+ for (const event of considered) {
3336
+ const parsed = parseEspnEvent(event, ctx);
3337
+ if (parsed.kind !== "valid") {
3338
+ if (parsed.kind !== "definitive-none") complete = false;
3339
+ continue;
3340
+ }
3341
+ if (seenIds.has(parsed.value.id)) {
3342
+ complete = false;
3343
+ continue;
3344
+ }
3345
+ seenIds.add(parsed.value.id);
3346
+ items.push(parsed.value);
3347
+ }
3348
+ return {
3349
+ items,
3350
+ total,
3351
+ shown: items.length,
3352
+ truncated: total > considered.length,
3353
+ // Some record was unreadable, or the window did not cover the payload, or
3354
+ // the envelope itself was not a list — none of those is a complete account
3355
+ // of what the provider sent.
3356
+ complete
3357
+ };
3074
3358
  }
3075
- function statVal(stats, name) {
3076
- const v = stats?.find((s) => s.name === name)?.value;
3077
- return typeof v === "number" && Number.isFinite(v) ? Math.round(v) : 0;
3359
+ function statVal(stats, name, signed = false) {
3360
+ if (!Array.isArray(stats) || stats.length > 64) return void 0;
3361
+ const matches = takeBounded(stats, 64).filter(
3362
+ (s) => s?.name === name
3363
+ );
3364
+ if (matches.length !== 1) return void 0;
3365
+ const v = matches[0]?.value;
3366
+ if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v)) return void 0;
3367
+ const limit = 1e3;
3368
+ return v > limit || v < (signed ? -limit : 0) ? void 0 : v;
3369
+ }
3370
+ function optionalStatVal(stats, name) {
3371
+ if (!Array.isArray(stats) || stats.length > 64) return void 0;
3372
+ const matches = takeBounded(stats, 64).filter(
3373
+ (s) => s?.name === name
3374
+ );
3375
+ if (matches.length === 0) return 0;
3376
+ if (matches.length !== 1) return void 0;
3377
+ const v = matches[0]?.value;
3378
+ return typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 1e3 ? v : void 0;
3078
3379
  }
3079
3380
  function entryToRow(e) {
3080
- return {
3081
- team: toTeam(e.team),
3082
- played: statVal(e.stats, "gamesPlayed"),
3083
- won: statVal(e.stats, "wins"),
3084
- drawn: statVal(e.stats, "ties"),
3085
- lost: statVal(e.stats, "losses"),
3086
- goalsFor: statVal(e.stats, "pointsFor"),
3087
- goalsAgainst: statVal(e.stats, "pointsAgainst"),
3088
- goalDiff: statVal(e.stats, "pointDifferential"),
3089
- points: statVal(e.stats, "points")
3090
- };
3381
+ const names = teamNames(e?.team);
3382
+ if (names.length === 0) return definitiveNone("standings entry names no team");
3383
+ const name = names[0];
3384
+ const code = teamCode(e?.team?.abbreviation, name);
3385
+ const played = statVal(e.stats, "gamesPlayed");
3386
+ const won = statVal(e.stats, "wins");
3387
+ const drawn = statVal(e.stats, "ties");
3388
+ const lost = statVal(e.stats, "losses");
3389
+ const goalsFor = statVal(e.stats, "pointsFor");
3390
+ const goalsAgainst = statVal(e.stats, "pointsAgainst");
3391
+ const goalDiff = statVal(e.stats, "pointDifferential", true);
3392
+ const points = statVal(e.stats, "points", true);
3393
+ const deductions = optionalStatVal(e.stats, "deductions");
3394
+ const providerRank = statVal(e.stats, "rank");
3395
+ if (played === void 0 || won === void 0 || drawn === void 0 || lost === void 0 || goalsFor === void 0 || goalsAgainst === void 0 || goalDiff === void 0 || points === void 0 || deductions === void 0 || providerRank === void 0 || providerRank < 1) {
3396
+ return malformed("standings entry has missing or invalid statistics");
3397
+ }
3398
+ if (played !== won + drawn + lost) {
3399
+ return malformed("standings entry games do not add up");
3400
+ }
3401
+ if (goalDiff !== goalsFor - goalsAgainst) {
3402
+ return malformed("standings entry goal difference does not add up");
3403
+ }
3404
+ if (points !== won * 3 + drawn - deductions) {
3405
+ return malformed("standings entry points do not add up");
3406
+ }
3407
+ return valid({
3408
+ team: { code, name, flag: productFlag(name) },
3409
+ played,
3410
+ won,
3411
+ drawn,
3412
+ lost,
3413
+ goalsFor,
3414
+ goalsAgainst,
3415
+ goalDiff,
3416
+ points,
3417
+ providerId: opaqueId(e?.team?.id, ESPN_ID),
3418
+ providerRank
3419
+ });
3091
3420
  }
3092
- function parseStandings(data) {
3421
+ function parseEspnStandings(raw) {
3422
+ const rawChildren = raw?.children;
3423
+ const readable = Array.isArray(rawChildren);
3424
+ const rawCount = readable ? rawChildren.length : 0;
3425
+ const children = takeBounded(rawChildren, MAX_GROUPS * 4);
3426
+ const sawAllChildren = rawCount === children.length;
3427
+ let rowsTruncated = false;
3428
+ let complete = readable && sawAllChildren;
3093
3429
  const out2 = [];
3094
- for (const child of data.children ?? []) {
3095
- const letter = (child.name ?? child.abbreviation ?? "").match(/Group\s+([A-L])/i)?.[1]?.toUpperCase();
3430
+ const seenGroups = /* @__PURE__ */ new Set();
3431
+ const seenProviderIds = /* @__PURE__ */ new Set();
3432
+ for (const child of children) {
3433
+ const label = humanLabel(child?.name ?? child?.abbreviation);
3434
+ const letter = label.match(/Group\s+([A-L])/i)?.[1]?.toUpperCase();
3096
3435
  if (!letter) continue;
3097
- const ranked = (child.standings?.entries ?? []).map((e) => ({
3098
- row: entryToRow(e),
3099
- rank: statVal(e.stats, "rank")
3100
- }));
3436
+ if (seenGroups.has(letter)) {
3437
+ complete = false;
3438
+ continue;
3439
+ }
3440
+ seenGroups.add(letter);
3441
+ const rawEntries = child?.standings?.entries;
3442
+ if (!Array.isArray(rawEntries) || rawEntries.length === 0) {
3443
+ complete = false;
3444
+ continue;
3445
+ }
3446
+ if (rawEntries.length > MAX_GROUP_ROWS) {
3447
+ rowsTruncated = true;
3448
+ complete = false;
3449
+ }
3450
+ const entries = takeBounded(rawEntries, MAX_GROUP_ROWS);
3451
+ const seenTeams = /* @__PURE__ */ new Set();
3452
+ const seenRanks = /* @__PURE__ */ new Set();
3453
+ const ranked = [];
3454
+ for (const e of entries) {
3455
+ const r = entryToRow(e);
3456
+ if (r.kind !== "valid") {
3457
+ if (r.kind !== "definitive-none") complete = false;
3458
+ continue;
3459
+ }
3460
+ const key = r.value.providerId ?? r.value.team.code;
3461
+ if (seenTeams.has(key) || seenRanks.has(r.value.providerRank) || r.value.providerId !== void 0 && seenProviderIds.has(r.value.providerId)) {
3462
+ complete = false;
3463
+ continue;
3464
+ }
3465
+ seenTeams.add(key);
3466
+ seenRanks.add(r.value.providerRank);
3467
+ if (r.value.providerId !== void 0) seenProviderIds.add(r.value.providerId);
3468
+ const { providerId: _dropId, providerRank: rank, ...row } = r.value;
3469
+ ranked.push({ row, rank });
3470
+ }
3101
3471
  ranked.sort((a, b) => {
3102
3472
  if (a.rank && b.rank && a.rank !== b.rank) return a.rank - b.rank;
3103
- const r = a.row;
3104
- const s = b.row;
3105
- return s.points - r.points || s.goalDiff - r.goalDiff || s.goalsFor - r.goalsFor || r.team.code.localeCompare(s.team.code);
3473
+ if (b.row.points !== a.row.points) return b.row.points - a.row.points;
3474
+ if (b.row.goalDiff !== a.row.goalDiff) return b.row.goalDiff - a.row.goalDiff;
3475
+ return b.row.goalsFor - a.row.goalsFor;
3106
3476
  });
3477
+ if (ranked.length === 0) {
3478
+ complete = false;
3479
+ continue;
3480
+ }
3107
3481
  out2.push({ group: letter, rows: ranked.map((x) => x.row) });
3108
3482
  }
3109
- out2.sort((a, b) => a.group.localeCompare(b.group));
3110
- return out2;
3483
+ return {
3484
+ items: out2,
3485
+ total: seenGroups.size,
3486
+ shown: out2.length,
3487
+ // We stopped early if the child list or any single group's rows were cut.
3488
+ truncated: !sawAllChildren || rowsTruncated,
3489
+ complete: complete && !rowsTruncated
3490
+ };
3491
+ }
3492
+ var ESPN_SOCCER = "https://site.api.espn.com/apis/site/v2/sports/soccer";
3493
+ var DEFAULT_COMPETITION = "fifa.world";
3494
+ var DEFAULT_BASE = `${ESPN_SOCCER}/${DEFAULT_COMPETITION}`;
3495
+ var USER_AGENT = `claudinho/${"0.9.4"} (+https://github.com/arturogarrido/claudinho)`;
3496
+ var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
3497
+ function competitionBase(slug) {
3498
+ return `${ESPN_SOCCER}/${slug}`;
3499
+ }
3500
+ var DEFAULT_TIMEOUT_MS = 6e3;
3501
+ var STANDINGS_SHARE_MS = 3e4;
3502
+ var ProviderError = class extends Error {
3503
+ kind;
3504
+ status;
3505
+ constructor(message, kind, status) {
3506
+ super(message);
3507
+ this.name = "ProviderError";
3508
+ this.kind = kind;
3509
+ this.status = status;
3510
+ }
3511
+ /** 429/403 — the upstream is refusing us; retrying at the live cadence makes it worse. */
3512
+ get throttled() {
3513
+ return this.kind === "http" && (this.status === 429 || this.status === 403);
3514
+ }
3515
+ };
3516
+ function toEspnDate(d) {
3517
+ return d.replace(/\D/g, "").slice(0, 8);
3518
+ }
3519
+ function usableProviderItems(kind, parsed, hasUsableRecord = parsed.items.length > 0) {
3520
+ if (!hasUsableRecord && (!parsed.complete || parsed.total > 0)) {
3521
+ throw new ProviderError(`ESPN ${kind} payload had no readable records`, "parse");
3522
+ }
3523
+ return [...parsed.items];
3111
3524
  }
3112
3525
  var EspnAdapter = class {
3113
3526
  constructor(opts = {}) {
3114
3527
  this.opts = opts;
3528
+ const expected = opts.expectedStandingsGroups ?? (opts.baseUrl === void 0 ? groups() : void 0);
3529
+ this.expectedStandingsGroups = expected ? [...expected] : void 0;
3530
+ this.standingsFallbackGroups = opts.baseUrl === void 0 && expected ? [...expected] : void 0;
3115
3531
  }
3116
3532
  opts;
3117
3533
  name = "espn";
3118
3534
  capabilities = { push: false, latencyHintSec: 45 };
3119
- /** Cached team-code -> group-letter map (built lazily from standings). */
3535
+ expectedStandingsGroups;
3536
+ standingsFallbackGroups;
3537
+ /** Short-lived team-code -> group-letter map (built lazily from standings). */
3120
3538
  groupMap;
3121
3539
  /**
3122
3540
  * One in-flight/recent standings fetch shared by fetchStandings and
@@ -3159,38 +3577,48 @@ var EspnAdapter = class {
3159
3577
  if (this.standingsShared && now - this.standingsShared.at < STANDINGS_SHARE_MS) {
3160
3578
  return this.standingsShared.promise;
3161
3579
  }
3162
- const promise = this.get(this.standingsUrl()).then(
3163
- (d) => parseStandings(d)
3164
- );
3580
+ const promise = this.get(this.standingsUrl()).then((d) => {
3581
+ const parsed = parseEspnStandings(d);
3582
+ return usableProviderItems(
3583
+ "standings",
3584
+ parsed,
3585
+ parsed.items.some((table) => table.rows.length > 0)
3586
+ );
3587
+ });
3165
3588
  this.standingsShared = { at: now, promise };
3166
- promise.catch(() => {
3589
+ void promise.catch(() => {
3167
3590
  if (this.standingsShared?.promise === promise) this.standingsShared = void 0;
3168
3591
  });
3169
3592
  return promise;
3170
3593
  }
3171
3594
  /**
3172
3595
  * Authoritative, cumulative group tables from the standings endpoint. Throws
3173
- * on fetch/parse failure (the caller decides the fallback). Group-stage only:
3174
- * non-group `children` are filtered out by {@link parseStandings}.
3596
+ * on fetch failure. Group-stage only: non-group `children` are filtered out
3597
+ * by {@link parseStandings}; malformed rows are omitted without hiding their
3598
+ * readable siblings.
3175
3599
  */
3176
3600
  async fetchStandings() {
3177
3601
  return this.sharedStandings();
3178
3602
  }
3179
3603
  /**
3180
- * Build (and cache) a team-code -> group-letter map from the standings
3604
+ * Build (and briefly cache) a team-code -> group-letter map from the standings
3181
3605
  * endpoint. Best-effort: returns {} if standings are unavailable — but a
3182
- * transient failure is NOT cached (only a successful parse pins the map), so
3183
- * one blip can't silently drop group letters for the adapter's lifetime.
3606
+ * transient failure is NOT cached, and a partial successful parse expires at
3607
+ * the standings TTL, so neither can silently drop group letters for the
3608
+ * adapter's lifetime.
3184
3609
  * Reuses the same parse/fetch as {@link fetchStandings}, so the two never
3185
3610
  * drift and one command never fetches standings twice.
3186
3611
  */
3187
3612
  async fetchGroupMap(force = false) {
3188
- if (this.groupMap && !force) return this.groupMap;
3613
+ const now = Date.now();
3614
+ if (!force && this.groupMap && now - this.groupMap.at < STANDINGS_SHARE_MS) {
3615
+ return this.groupMap.value;
3616
+ }
3189
3617
  try {
3190
3618
  const tables = await this.sharedStandings();
3191
3619
  const map = {};
3192
3620
  for (const t2 of tables) for (const r of t2.rows) map[r.team.code] = t2.group;
3193
- this.groupMap = map;
3621
+ this.groupMap = { at: Date.now(), value: map };
3194
3622
  return map;
3195
3623
  } catch {
3196
3624
  return {};
@@ -3205,7 +3633,8 @@ var EspnAdapter = class {
3205
3633
  this.opts.enrichGroups === false ? Promise.resolve({}) : this.fetchGroupMap(),
3206
3634
  this.get(url.toString())
3207
3635
  ]);
3208
- return (data.events ?? []).map((ev) => mapEspnEvent(ev, { groupByTeam }));
3636
+ const parsed = parseEspnEvents(data, { groupByTeam });
3637
+ return usableProviderItems("scoreboard", parsed);
3209
3638
  }
3210
3639
  async get(url) {
3211
3640
  const doFetch = this.opts.fetchImpl ?? fetch;
@@ -4015,17 +4444,26 @@ async function getMatchesForDate(adapter, dateISO) {
4015
4444
  }
4016
4445
  async function getStandings(adapter, group) {
4017
4446
  const want = group?.toUpperCase();
4447
+ const expected = adapter.expectedStandingsGroups;
4448
+ if (want && expected && !expected.includes(want)) {
4449
+ return { tables: [], degraded: false };
4450
+ }
4018
4451
  if (adapter.fetchStandings) {
4019
4452
  try {
4020
4453
  const all = await adapter.fetchStandings();
4021
4454
  const tables2 = (want ? all.filter((t2) => t2.group === want) : all).sort(
4022
4455
  (a, b) => a.group.localeCompare(b.group)
4023
4456
  );
4024
- return { tables: tables2, degraded: false, source: adapter.name };
4457
+ const availableGroups = new Set(tables2.map((table) => table.group));
4458
+ const expectedGroupWasOmitted = want ? (expected?.includes(want) ?? false) && tables2.length === 0 : expected?.some((group2) => !availableGroups.has(group2)) ?? false;
4459
+ if (!expectedGroupWasOmitted) {
4460
+ return { tables: tables2, degraded: false, source: adapter.name };
4461
+ }
4025
4462
  } catch {
4026
4463
  }
4027
4464
  }
4028
- const letters = want ? [want] : groups();
4465
+ const fallbackGroups = adapter.standingsFallbackGroups;
4466
+ const letters = fallbackGroups ? want ? fallbackGroups.includes(want) ? [want] : [] : [...new Set(fallbackGroups)].sort((a, b) => a.localeCompare(b)) : [];
4029
4467
  const tables = letters.map((g) => ({ group: g, rows: rosterAtZero(fixturesByGroup(g)) })).filter((t2) => t2.rows.length > 0);
4030
4468
  return { tables, degraded: true };
4031
4469
  }
@@ -4082,7 +4520,10 @@ async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Dat
4082
4520
  try {
4083
4521
  const win = knockoutWindow();
4084
4522
  if (adapter.fetchWindow && win) {
4085
- fixtures = mergeLive(fixtures, await adapter.fetchWindow(win.start, win.end));
4523
+ fixtures = mergeLive(
4524
+ fixtures,
4525
+ await adapter.fetchWindow(win.start, win.end)
4526
+ );
4086
4527
  }
4087
4528
  } catch {
4088
4529
  overlayFailed = true;
@@ -4146,14 +4587,144 @@ async function getMatchById(adapter, id) {
4146
4587
  async function getLiveMatches(adapter, now = /* @__PURE__ */ new Date()) {
4147
4588
  try {
4148
4589
  const day = now.toISOString().slice(0, 10);
4149
- const matches = adapter.fetchWindow ? (await adapter.fetchWindow(shiftUtcDate(day, -1), shiftUtcDate(day, 1))).filter(
4150
- (m) => isLive(m.status)
4151
- ) : await adapter.fetchLive();
4590
+ const matches = (adapter.fetchWindow ? await adapter.fetchWindow(shiftUtcDate(day, -1), shiftUtcDate(day, 1)) : await adapter.fetchLive()).filter((m) => isLive(m.status));
4152
4591
  return { matches, degraded: false, source: adapter.name };
4153
4592
  } catch {
4154
4593
  return { matches: [], degraded: true };
4155
4594
  }
4156
4595
  }
4596
+ function pct(p) {
4597
+ return Math.round(p * 100);
4598
+ }
4599
+ var KNOWN_MARKET_SOURCES = ["polymarket", "fake"];
4600
+ function marketSourceLabel(source) {
4601
+ if (source === "polymarket") return "Polymarket";
4602
+ if (source === "fake") return "demo data";
4603
+ return source.charAt(0).toUpperCase() + source.slice(1);
4604
+ }
4605
+ function outcomeLabel(o, match) {
4606
+ if (o.kind === "home") return match.home.name;
4607
+ if (o.kind === "away") return match.away.name;
4608
+ if (o.kind === "draw") return "Draw";
4609
+ return o.label;
4610
+ }
4611
+ function utcHhmm(iso) {
4612
+ const t2 = Date.parse(iso);
4613
+ if (!Number.isFinite(t2)) return "";
4614
+ return `${new Date(t2).toISOString().slice(11, 16)} UTC`;
4615
+ }
4616
+ function marketFavoriteText(signal, match) {
4617
+ const fav = signal.favorite;
4618
+ if (!fav || fav.strength === "close") return "Prediction markets see this match as close.";
4619
+ if (fav.kind === "draw") return "Prediction markets see a draw as the top outcome.";
4620
+ const name = fav.kind === "home" ? match.home.name : match.away.name;
4621
+ return fav.strength === "clear" ? `Prediction markets favor ${name}.` : `Prediction markets slightly favor ${name}.`;
4622
+ }
4623
+ function marketProbabilityText(signal, match) {
4624
+ const order = ["home", "draw", "away"];
4625
+ const parts = [];
4626
+ for (const kind of order) {
4627
+ const o = signal.outcomes.find((x) => x.kind === kind);
4628
+ if (o) parts.push(`${outcomeLabel(o, match)} ${pct(o.probability)}%`);
4629
+ }
4630
+ for (const o of signal.outcomes) {
4631
+ if (o.kind === "other") parts.push(`${outcomeLabel(o, match)} ${pct(o.probability)}%`);
4632
+ }
4633
+ return parts.join(" \xB7 ");
4634
+ }
4635
+ function marketAttributionText(signal) {
4636
+ const time = utcHhmm(signal.asOf);
4637
+ const src = `Source: ${marketSourceLabel(signal.source)}`;
4638
+ return time ? `${src} \xB7 updated ${time}` : src;
4639
+ }
4640
+ function marketLine(signal, match) {
4641
+ return `Market: ${marketProbabilityText(signal, match)} \xB7 ${marketSourceLabel(
4642
+ signal.source
4643
+ )} \xB7 informational only`;
4644
+ }
4645
+ function marketBlock(signal, match) {
4646
+ const lines = [];
4647
+ if (signal.stale) lines.push("Market signal is stale; the reading may be out of date.");
4648
+ lines.push(marketFavoriteText(signal, match));
4649
+ lines.push(marketProbabilityText(signal, match));
4650
+ lines.push(`${marketAttributionText(signal)} \xB7 informational only`);
4651
+ return lines;
4652
+ }
4653
+ var MAX_OUTCOMES = 128;
4654
+ var MATCH_ID = /^[0-9]{1,20}$/;
4655
+ var MARKET_ID = /^(?:[0-9]{1,32}|fifwc-[a-z]{2,3}-[a-z]{2,3}-\d{4}-\d{2}-\d{2})$/;
4656
+ var OUTCOME_KINDS = /* @__PURE__ */ new Set(["home", "draw", "away", "other"]);
4657
+ var TEAM_CODE_COLUMNS2 = 8;
4658
+ function sealOutcome(raw) {
4659
+ if (!raw || typeof raw !== "object") return void 0;
4660
+ const o = raw;
4661
+ const kind = member(o.kind, OUTCOME_KINDS);
4662
+ const p = probability(o.probability);
4663
+ if (!kind || p === void 0) return void 0;
4664
+ const out2 = { kind };
4665
+ if (o.teamCode !== void 0) {
4666
+ if (typeof o.teamCode !== "string") return void 0;
4667
+ out2.teamCode = humanLabel(o.teamCode, TEAM_CODE_COLUMNS2);
4668
+ }
4669
+ out2.label = humanLabel(o.label);
4670
+ out2.probability = p;
4671
+ if ((out2.kind === "home" || out2.kind === "away") && !out2.teamCode) return void 0;
4672
+ return out2;
4673
+ }
4674
+ function hasDuplicateKind(outcomes) {
4675
+ const seen = /* @__PURE__ */ new Set();
4676
+ for (const o of outcomes) {
4677
+ if (o.kind === "other") continue;
4678
+ if (seen.has(o.kind)) return true;
4679
+ seen.add(o.kind);
4680
+ }
4681
+ return false;
4682
+ }
4683
+ function sealMarketSignal(raw, options = {}) {
4684
+ if (!raw || typeof raw !== "object") return malformed("signal is not an object");
4685
+ const s = raw;
4686
+ const matchId = opaqueId(s.matchId, MATCH_ID);
4687
+ if (!matchId) return malformed("signal names no fixture");
4688
+ if (!Array.isArray(s.outcomes) || s.outcomes.length > MAX_OUTCOMES) {
4689
+ return malformed("signal outcomes are absent or exceed the cap");
4690
+ }
4691
+ const outcomes = [];
4692
+ for (const rawOutcome of takeBounded(s.outcomes, MAX_OUTCOMES)) {
4693
+ const outcome = sealOutcome(rawOutcome);
4694
+ if (!outcome) return malformed("signal carries an unreadable outcome");
4695
+ outcomes.push(outcome);
4696
+ }
4697
+ if (hasDuplicateKind(outcomes)) {
4698
+ return ambiguous("two outcomes claim the same result");
4699
+ }
4700
+ const sourceMarketId = opaqueId(s.sourceMarketId, MARKET_ID);
4701
+ const liquidity = quantity(s.liquidity);
4702
+ const volume24h = quantity(s.volume24h);
4703
+ const out2 = {
4704
+ matchId,
4705
+ // Allow-listed, not merely stripped: this lands in the provider-attribution
4706
+ // slot, where `marketSourceLabel` falls through to the raw string for an
4707
+ // unrecognized provider — attacker prose where the reader expects
4708
+ // "Polymarket".
4709
+ source: member(s.source, new Set(KNOWN_MARKET_SOURCES)) ?? ""
4710
+ };
4711
+ if (sourceMarketId) out2.sourceMarketId = sourceMarketId;
4712
+ out2.asOf = canonicalTimestamp(s.asOf) ?? "";
4713
+ out2.fetchedAt = canonicalTimestamp(s.fetchedAt) ?? "";
4714
+ out2.outcomes = outcomes;
4715
+ const isAmbiguous = s.ambiguous !== false;
4716
+ const favorite = isAmbiguous ? void 0 : deriveFavorite(outcomes);
4717
+ if (favorite) out2.favorite = favorite;
4718
+ if (liquidity !== void 0) out2.liquidity = liquidity;
4719
+ if (volume24h !== void 0) out2.volume24h = volume24h;
4720
+ out2.stale = s.stale !== false;
4721
+ out2.ambiguous = isAmbiguous || out2.source === "";
4722
+ out2.stale = out2.stale || isStaleSignal(out2, { now: options.now, maxAgeMs: options.maxAgeMs });
4723
+ return valid(out2);
4724
+ }
4725
+ function parseCachedMarketSignal(raw, options = {}) {
4726
+ return sealMarketSignal(raw, options);
4727
+ }
4157
4728
  var DEFAULT_MAX_AGE_MS = 15 * 6e4;
4158
4729
  function marketRelevant(match, now = /* @__PURE__ */ new Date()) {
4159
4730
  if (isLive(match.status)) return true;
@@ -4172,9 +4743,9 @@ function normalizeOutcomes(outcomes) {
4172
4743
  probability: Number.isFinite(o.probability) && o.probability > 0 ? o.probability / sum : 0
4173
4744
  }));
4174
4745
  }
4175
- function favoriteStrength(probability) {
4176
- if (probability >= 0.65) return "clear";
4177
- if (probability >= 0.52) return "slight";
4746
+ function favoriteStrength(probability2) {
4747
+ if (probability2 >= 0.65) return "clear";
4748
+ if (probability2 >= 0.52) return "slight";
4178
4749
  return "close";
4179
4750
  }
4180
4751
  function deriveFavorite(outcomes) {
@@ -4193,14 +4764,16 @@ function deriveFavorite(outcomes) {
4193
4764
  }
4194
4765
  function mapsCleanly(match, outcomes) {
4195
4766
  if (outcomes.some((o) => o.kind === "other")) return false;
4767
+ const kinds = outcomes.map((o) => o.kind);
4768
+ if (new Set(kinds).size !== kinds.length) return false;
4196
4769
  const home = outcomes.find((o) => o.kind === "home");
4197
4770
  const away = outcomes.find((o) => o.kind === "away");
4198
4771
  const draw = outcomes.find((o) => o.kind === "draw");
4199
4772
  if (!home || !away) return false;
4200
- if (home.teamCode && home.teamCode.toUpperCase() !== match.home.code.toUpperCase()) {
4773
+ if (!home.teamCode || home.teamCode.toUpperCase() !== match.home.code.toUpperCase()) {
4201
4774
  return false;
4202
4775
  }
4203
- if (away.teamCode && away.teamCode.toUpperCase() !== match.away.code.toUpperCase()) {
4776
+ if (!away.teamCode || away.teamCode.toUpperCase() !== match.away.code.toUpperCase()) {
4204
4777
  return false;
4205
4778
  }
4206
4779
  if (match.stage === "GROUP" && !draw) return false;
@@ -4215,11 +4788,13 @@ function hasSaneDistribution(outcomes) {
4215
4788
  const sum = priced.reduce((s, o) => s + o.probability, 0);
4216
4789
  return sum > 0.97 && sum < 1.03;
4217
4790
  }
4791
+ var FUTURE_SKEW_MS = 6e4;
4218
4792
  function isStaleSignal(signal, options = {}) {
4219
4793
  const maxAge = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
4220
4794
  const asOf = Date.parse(signal.asOf);
4221
4795
  if (!Number.isFinite(asOf)) return true;
4222
4796
  const now = (options.now ?? /* @__PURE__ */ new Date()).getTime();
4797
+ if (asOf - now > FUTURE_SKEW_MS) return true;
4223
4798
  return now - asOf > maxAge;
4224
4799
  }
4225
4800
  function isReliableMarketSignal(signal, options = {}) {
@@ -4235,8 +4810,8 @@ function isReliableMarketSignal(signal, options = {}) {
4235
4810
  }
4236
4811
  function buildMarketSignal(input) {
4237
4812
  const outcomes = normalizeOutcomes(input.outcomes);
4238
- const ambiguous = input.ambiguous === true || !mapsCleanly(input.match, outcomes);
4239
- const favorite = ambiguous ? void 0 : deriveFavorite(outcomes);
4813
+ const ambiguous2 = input.ambiguous === true || !mapsCleanly(input.match, outcomes);
4814
+ const favorite = ambiguous2 ? void 0 : deriveFavorite(outcomes);
4240
4815
  const signal = {
4241
4816
  matchId: input.match.id,
4242
4817
  source: input.source,
@@ -4248,66 +4823,33 @@ function buildMarketSignal(input) {
4248
4823
  liquidity: input.liquidity,
4249
4824
  volume24h: input.volume24h,
4250
4825
  stale: false,
4251
- ambiguous
4826
+ ambiguous: ambiguous2
4252
4827
  };
4253
4828
  signal.stale = isStaleSignal(signal, { now: input.now, maxAgeMs: input.maxAgeMs });
4254
- return signal;
4255
- }
4256
- function pct(p) {
4257
- return Math.round(p * 100);
4258
- }
4259
- function marketSourceLabel(source) {
4260
- if (source === "polymarket") return "Polymarket";
4261
- if (source === "fake") return "demo data";
4262
- return source.charAt(0).toUpperCase() + source.slice(1);
4263
- }
4264
- function outcomeLabel(o, match) {
4265
- if (o.kind === "home") return match.home.name;
4266
- if (o.kind === "away") return match.away.name;
4267
- if (o.kind === "draw") return "Draw";
4268
- return o.label;
4269
- }
4270
- function utcHhmm(iso) {
4271
- const t2 = Date.parse(iso);
4272
- if (!Number.isFinite(t2)) return "";
4273
- return `${new Date(t2).toISOString().slice(11, 16)} UTC`;
4274
- }
4275
- function marketFavoriteText(signal, match) {
4276
- const fav = signal.favorite;
4277
- if (!fav || fav.strength === "close") return "Prediction markets see this match as close.";
4278
- if (fav.kind === "draw") return "Prediction markets see a draw as the top outcome.";
4279
- const name = fav.kind === "home" ? match.home.name : match.away.name;
4280
- return fav.strength === "clear" ? `Prediction markets favor ${name}.` : `Prediction markets slightly favor ${name}.`;
4281
- }
4282
- function marketProbabilityText(signal, match) {
4283
- const order = ["home", "draw", "away"];
4284
- const parts = [];
4285
- for (const kind of order) {
4286
- const o = signal.outcomes.find((x) => x.kind === kind);
4287
- if (o) parts.push(`${outcomeLabel(o, match)} ${pct(o.probability)}%`);
4829
+ const sealed = sealMarketSignal(signal, { now: input.now, maxAgeMs: input.maxAgeMs });
4830
+ if (sealed.kind !== "valid") {
4831
+ return { ...signal, outcomes: [], favorite: void 0, stale: true, ambiguous: true };
4288
4832
  }
4289
- for (const o of signal.outcomes) {
4290
- if (o.kind === "other") parts.push(`${outcomeLabel(o, match)} ${pct(o.probability)}%`);
4291
- }
4292
- return parts.join(" \xB7 ");
4833
+ return { ...sealed.value, ambiguous: sealed.value.ambiguous || ambiguous2 };
4293
4834
  }
4294
- function marketAttributionText(signal) {
4295
- const time = utcHhmm(signal.asOf);
4296
- const src = `Source: ${marketSourceLabel(signal.source)}`;
4297
- return time ? `${src} \xB7 updated ${time}` : src;
4835
+ var NONE = { kind: "none" };
4836
+ function selectOne(candidates) {
4837
+ if (candidates.length === 1) return { kind: "one", value: candidates[0] };
4838
+ if (candidates.length === 0) return NONE;
4839
+ return { kind: "ambiguous", count: candidates.length };
4298
4840
  }
4299
- function marketLine(signal, match) {
4300
- return `Market: ${marketProbabilityText(signal, match)} \xB7 ${marketSourceLabel(
4301
- signal.source
4302
- )} \xB7 informational only`;
4841
+ function resolvedValues(batch) {
4842
+ const out2 = /* @__PURE__ */ new Map();
4843
+ for (const [key, r] of batch.results) if (r.kind === "valid") out2.set(key, r.value);
4844
+ return out2;
4303
4845
  }
4304
- function marketBlock(signal, match) {
4305
- const lines = [];
4306
- if (signal.stale) lines.push("Market signal is stale; the reading may be out of date.");
4307
- lines.push(marketFavoriteText(signal, match));
4308
- lines.push(marketProbabilityText(signal, match));
4309
- lines.push(`${marketAttributionText(signal)} \xB7 informational only`);
4310
- return lines;
4846
+ function cacheableKeys(batch) {
4847
+ const out2 = /* @__PURE__ */ new Set();
4848
+ for (const [key, r] of batch.results) if (isCacheable(r)) out2.add(key);
4849
+ return out2;
4850
+ }
4851
+ function emptyBatch() {
4852
+ return { results: /* @__PURE__ */ new Map(), complete: false };
4311
4853
  }
4312
4854
  var FakeMarketProvider = class {
4313
4855
  constructor(opts = {}) {
@@ -4322,14 +4864,12 @@ var FakeMarketProvider = class {
4322
4864
  return void 0;
4323
4865
  }
4324
4866
  async findSignals(matches, options) {
4325
- const signals = /* @__PURE__ */ new Map();
4326
- const checked = /* @__PURE__ */ new Set();
4867
+ const results = /* @__PURE__ */ new Map();
4327
4868
  for (const m of matches) {
4328
- checked.add(m.id);
4329
4869
  const s = await this.findSignal(m, options);
4330
- if (s) signals.set(m.id, s);
4870
+ results.set(m.id, s ? valid(s) : definitiveNone("fake provider has no signal"));
4331
4871
  }
4332
- return { signals, checked };
4872
+ return { results, complete: true };
4333
4873
  }
4334
4874
  synthesize(match, options) {
4335
4875
  const seed = hash(`${match.home.code}-${match.away.code}`);
@@ -4346,7 +4886,9 @@ var FakeMarketProvider = class {
4346
4886
  return buildMarketSignal({
4347
4887
  match,
4348
4888
  source: "fake",
4349
- sourceMarketId: `fake-${match.id}`,
4889
+ // Must satisfy the boundary's opaque-id grammar, like a real one:
4890
+ // a source id that only the live path accepts is the asymmetry itself.
4891
+ sourceMarketId: match.id,
4350
4892
  asOf,
4351
4893
  fetchedAt: now.toISOString(),
4352
4894
  outcomes,
@@ -4370,6 +4912,8 @@ var DEFAULT_BASE2 = "https://gamma-api.polymarket.com";
4370
4912
  var ALLOWED_HOSTS = /* @__PURE__ */ new Set(["gamma-api.polymarket.com"]);
4371
4913
  var USER_AGENT2 = "claudinho/0.0 (+https://github.com/arturogarrido/claudinho)";
4372
4914
  var DEFAULT_TIMEOUT_MS2 = 8e3;
4915
+ var MAX_EVENT_MARKETS = 256;
4916
+ var DEFAULT_DEADLINE_MS = 15e3;
4373
4917
  var WC_SERIES_SLUG = "soccer-fifwc";
4374
4918
  var WC_SPORT = "fifwc";
4375
4919
  var KICKOFF_TOLERANCE_MS = 6 * 60 * 6e4;
@@ -4382,49 +4926,81 @@ var PolymarketProvider = class {
4382
4926
  opts;
4383
4927
  name = "polymarket";
4384
4928
  async findSignal(match, options) {
4385
- const deadline = options?.deadlineMs != null ? Date.now() + options.deadlineMs : Number.POSITIVE_INFINITY;
4386
- return (await this.resolveOne(match, options, deadline)).signal;
4929
+ const deadline = Date.now() + (options?.deadlineMs ?? DEFAULT_DEADLINE_MS);
4930
+ return parsedValue(await this.resolveOne(match, options, deadline));
4387
4931
  }
4388
4932
  async findSignals(matches, options) {
4389
- const signals = /* @__PURE__ */ new Map();
4390
- const checked = /* @__PURE__ */ new Set();
4391
- const deadline = options?.deadlineMs != null ? Date.now() + options.deadlineMs : Number.POSITIVE_INFINITY;
4933
+ const results = /* @__PURE__ */ new Map();
4934
+ const deadline = Date.now() + (options?.deadlineMs ?? DEFAULT_DEADLINE_MS);
4935
+ let complete = true;
4392
4936
  for (const m of matches) {
4393
- if (Date.now() >= deadline) break;
4937
+ if (Date.now() >= deadline) {
4938
+ results.set(m.id, unresolved("enrichment deadline expired"));
4939
+ complete = false;
4940
+ continue;
4941
+ }
4394
4942
  const r = await this.resolveOne(m, options, deadline);
4395
- if (r.checked) checked.add(m.id);
4396
- if (r.signal) signals.set(m.id, r.signal);
4943
+ if (r.kind === "unresolved" || r.kind === "malformed") complete = false;
4944
+ results.set(m.id, r);
4397
4945
  }
4398
- return { signals, checked };
4946
+ return { results, complete };
4399
4947
  }
4400
4948
  /**
4401
- * Resolve one match. `checked` distinguishes a DEFINITIVE result (reached the
4402
- * source and found no usable market, or the fixture is unmappable) from a
4403
- * provider/network error so transient failures are retried, not
4404
- * negative-cached.
4949
+ * Resolve one match into a verdict.
4950
+ *
4951
+ * Every exit says which KIND of non-answer it is, because that decides
4952
+ * whether it may be remembered — see `isCacheable`: a conclusion we drew from
4953
+ * a payload we READ is cacheable (including an ambiguity, which is stable),
4954
+ * while a shape we could not read is not. Previously a single
4955
+ * `checked: boolean` collapsed five distinct situations into two, and the
4956
+ * ones that landed on the wrong side of it — an ambiguous payload, a
4957
+ * two-legged market, an incoherent 1X2 — were negative-cached as the fact
4958
+ * that this fixture has no market.
4405
4959
  */
4406
4960
  async resolveOne(match, options, deadline = Number.POSITIVE_INFINITY) {
4407
- const entry = (this.opts.mapping ?? BUNDLED_MAPPING)[match.id];
4408
- const slugs = entry?.eventSlug ? [entry.eventSlug] : deriveEventSlugs(match);
4409
- if (slugs.length === 0) return { checked: true };
4410
4961
  const configured = options?.timeoutMs ?? this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
4411
4962
  try {
4963
+ const entry = (this.opts.mapping ?? BUNDLED_MAPPING)[match.id];
4964
+ const slugs = entry?.eventSlug ? [entry.eventSlug] : deriveEventSlugs(match);
4965
+ if (slugs.length === 0) return definitiveNone("fixture has no derivable event slug");
4966
+ const RANK = {
4967
+ "definitive-none": 0,
4968
+ ambiguous: 1,
4969
+ unresolved: 2,
4970
+ malformed: 3
4971
+ };
4972
+ let worst;
4973
+ const keepWorst = (r) => {
4974
+ if (r.kind === "definitive-none" || r.kind === "valid") return;
4975
+ if (!worst || (RANK[r.kind] ?? 0) > (RANK[worst.kind] ?? 0)) worst = r;
4976
+ };
4412
4977
  for (const slug of slugs) {
4413
4978
  const remaining = deadline - Date.now();
4414
- if (remaining <= 0) return { checked: false };
4415
- const event = await this.fetchEvent(slug, Math.min(configured, remaining));
4416
- const signal = event ? this.toSignal(match, slug, event, options) : void 0;
4417
- if (signal) return { signal, checked: true };
4979
+ if (remaining <= 0) return unresolved("deadline expired between candidate slugs");
4980
+ let found;
4981
+ try {
4982
+ found = await this.fetchEvent(slug, Math.min(configured, remaining));
4983
+ } catch {
4984
+ keepWorst(malformed("candidate request failed"));
4985
+ continue;
4986
+ }
4987
+ if (found.kind !== "valid") {
4988
+ keepWorst(found);
4989
+ continue;
4990
+ }
4991
+ const r = this.toSignal(match, slug, found.value, options);
4992
+ if (r.kind === "valid") return r;
4993
+ keepWorst(r);
4418
4994
  }
4419
- return { checked: true };
4995
+ return worst ?? definitiveNone("no candidate slug yielded a usable market");
4420
4996
  } catch {
4421
- return { checked: false };
4997
+ return malformed("provider request failed");
4422
4998
  }
4423
4999
  }
4424
5000
  async fetchEvent(slug, timeoutMs) {
4425
5001
  const base = this.opts.baseUrl ?? DEFAULT_BASE2;
4426
5002
  assertAllowedHost(base);
4427
- const url = `${base}/events?slug=${encodeURIComponent(slug)}`;
5003
+ const url = `${base}/events/slug/${encodeURIComponent(slug)}`;
4428
5004
  const doFetch = this.opts.fetchImpl ?? fetch;
4429
5005
  const res = await doFetch(url, {
4430
5006
  signal: AbortSignal.timeout(timeoutMs ?? this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2),
@@ -4433,7 +5009,7 @@ var PolymarketProvider = class {
4433
5009
  redirect: "error",
4434
5010
  headers: { Accept: "application/json", "User-Agent": USER_AGENT2 }
4435
5011
  });
4436
- if (res.status === 404) return void 0;
5012
+ if (res.status === 404) return definitiveNone("slug returns 404");
4437
5013
  if (!res.ok) {
4438
5014
  throw new Error(`Polymarket request failed: ${res.status} ${res.statusText}`);
4439
5015
  }
@@ -4442,63 +5018,148 @@ var PolymarketProvider = class {
4442
5018
  throw new Error(`Polymarket response too large: ${length} bytes`);
4443
5019
  }
4444
5020
  const data = await res.json();
5021
+ if (Array.isArray(data) && data.length > 1) {
5022
+ return ambiguous("slug returned more than one event");
5023
+ }
5024
+ if (Array.isArray(data) && data.length === 0) return definitiveNone("slug returns no event");
4445
5025
  const event = Array.isArray(data) ? data[0] : data;
4446
- return event && typeof event === "object" ? event : void 0;
5026
+ if (!event || typeof event !== "object") return malformed("event body is not an object");
5027
+ return valid(event);
4447
5028
  }
4448
5029
  toSignal(match, eventSlug, event, options) {
4449
- if (event.active === false || event.closed === true) return void 0;
4450
- if (event.seriesSlug != null && event.seriesSlug !== WC_SERIES_SLUG && event.sport?.sport !== WC_SPORT) {
4451
- return void 0;
5030
+ if (typeof event.active !== "boolean" || typeof event.closed !== "boolean") {
5031
+ return malformed("event active/closed is not a boolean");
5032
+ }
5033
+ if (event.active === false || event.closed === true) {
5034
+ return definitiveNone("event is closed or inactive");
5035
+ }
5036
+ if (event.seriesSlug !== WC_SERIES_SLUG && event.sport?.sport !== WC_SPORT) {
5037
+ return definitiveNone("event is not in this competition");
5038
+ }
5039
+ if (typeof event.slug !== "string") {
5040
+ return malformed("event states no slug");
4452
5041
  }
4453
- if (event.slug != null && event.slug !== eventSlug) return void 0;
4454
- const start = event.startTime ? Date.parse(event.startTime) : Number.NaN;
5042
+ if (event.slug !== eventSlug) return definitiveNone("event is not the one requested");
5043
+ if (typeof event.startTime !== "string" || !canonicalTimestamp(event.startTime)) {
5044
+ return malformed("event startTime missing or unparseable");
5045
+ }
5046
+ const start = Date.parse(event.startTime);
4455
5047
  const kick = Date.parse(match.kickoff);
4456
- if (Number.isFinite(start) && Number.isFinite(kick) && Math.abs(start - kick) > KICKOFF_TOLERANCE_MS) {
4457
- return void 0;
5048
+ if (!Number.isFinite(start) || !Number.isFinite(kick)) {
5049
+ return malformed("event or fixture kickoff is unreadable");
5050
+ }
5051
+ if (Math.abs(start - kick) > KICKOFF_TOLERANCE_MS) {
5052
+ return definitiveNone("event kickoff does not match the fixture");
5053
+ }
5054
+ if (!Array.isArray(event.markets)) {
5055
+ return malformed("event markets is not an array");
4458
5056
  }
4459
- const moneyline = (event.markets ?? []).filter(
4460
- (m) => (m.sportsMarketType ?? "moneyline") === "moneyline"
5057
+ const marketsTruncated = Array.isArray(event.markets) && event.markets.length > MAX_EVENT_MARKETS;
5058
+ if (marketsTruncated) {
5059
+ return malformed("event market list exceeded the cap");
5060
+ }
5061
+ const marketList = takeBounded(event.markets, MAX_EVENT_MARKETS);
5062
+ if (marketList.some(
5063
+ (market) => !market || typeof market !== "object" || typeof market.sportsMarketType !== "string"
5064
+ )) {
5065
+ return malformed("event market is missing its market-type discriminator");
5066
+ }
5067
+ const moneyline = marketList.filter(
5068
+ (m) => m?.sportsMarketType === "moneyline"
4461
5069
  );
4462
- const homeMarket = pickMarket(moneyline, match.home.code, match.home.name);
4463
- const awayMarket = pickMarket(moneyline, match.away.code, match.away.name);
4464
- const drawMarket = pickDraw(moneyline);
4465
- if (!homeMarket || !awayMarket) return void 0;
5070
+ const homeSel = pickMarket(moneyline, match.home.code, match.home.name);
5071
+ const awaySel = pickMarket(moneyline, match.away.code, match.away.name);
5072
+ const drawSel = pickDraw(moneyline);
5073
+ for (const [side, sel] of [
5074
+ ["home", homeSel],
5075
+ ["away", awaySel],
5076
+ ["draw", drawSel]
5077
+ ]) {
5078
+ if (sel.kind === "ambiguous") {
5079
+ return ambiguous(`${sel.count} markets claim the ${side} outcome`);
5080
+ }
5081
+ }
5082
+ if (homeSel.kind !== "one" || awaySel.kind !== "one" || drawSel.kind !== "one") {
5083
+ return definitiveNone("event does not carry all three 1X2 legs");
5084
+ }
5085
+ const homeMarket = homeSel.value;
5086
+ const awayMarket = awaySel.value;
5087
+ const drawMarket = drawSel.value;
4466
5088
  const legIds = [homeMarket, awayMarket, drawMarket].filter((m) => m != null).map((m) => m.id ?? m.slug ?? "");
4467
- if (new Set(legIds).size !== legIds.length) return void 0;
5089
+ if (new Set(legIds).size !== legIds.length) {
5090
+ return ambiguous("two outcome legs are the same market");
5091
+ }
4468
5092
  const legs = [
4469
5093
  ["home", homeMarket, match.home.code, match.home.name],
4470
5094
  ["draw", drawMarket, void 0, "Draw"],
4471
5095
  ["away", awayMarket, match.away.code, match.away.name]
4472
5096
  ];
4473
5097
  const outcomes = [];
4474
- let asOf = event.updatedAt;
5098
+ let asOf = canonicalTimestamp(event.updatedAt);
4475
5099
  let liquidity;
4476
- for (const [kind, market, teamCode, label] of legs) {
5100
+ for (const [kind, market, teamCode2, label] of legs) {
4477
5101
  if (!market) continue;
4478
- if (market.closed === true || market.active === false) return void 0;
4479
- if (market.description && NON_REGULAR_TIME.test(market.description)) return void 0;
5102
+ if (typeof market.closed !== "boolean" || typeof market.active !== "boolean") {
5103
+ return malformed("market active/closed is not a boolean");
5104
+ }
5105
+ if (market.closed === true || market.active === false) {
5106
+ return definitiveNone("an outcome leg is closed or inactive");
5107
+ }
5108
+ if (market.description && NON_REGULAR_TIME.test(market.description)) {
5109
+ return definitiveNone("an outcome leg is not a regular-time market");
5110
+ }
4480
5111
  const yes = yesPrice(market);
4481
- if (yes == null) return void 0;
4482
- outcomes.push({ kind, teamCode, label, probability: yes });
4483
- if (market.updatedAt && (!asOf || market.updatedAt < asOf)) asOf = market.updatedAt;
4484
- const liq = numberish(market.liquidityNum ?? market.liquidity);
5112
+ if (yes == null) return malformed("market is not a readable Yes/No binary");
5113
+ outcomes.push({ kind, teamCode: teamCode2, label, probability: yes });
5114
+ const marketAsOf = canonicalTimestamp(market.updatedAt);
5115
+ if (!marketAsOf) {
5116
+ return malformed("market updatedAt missing or unparseable");
5117
+ }
5118
+ const nowMs = (options?.now ?? this.opts.now ?? /* @__PURE__ */ new Date()).getTime();
5119
+ if (Date.parse(marketAsOf) - nowMs > FUTURE_SKEW_MS) {
5120
+ return malformed("market updatedAt is dated forward");
5121
+ }
5122
+ if (!asOf || Date.parse(marketAsOf) < Date.parse(asOf)) asOf = marketAsOf;
5123
+ const rawLiq = market.liquidityNum ?? market.liquidity;
5124
+ const liq = numberish(rawLiq);
5125
+ if (rawLiq != null && liq == null) {
5126
+ return malformed("market liquidity is unreadable");
5127
+ }
4485
5128
  if (liq != null) liquidity = liquidity == null ? liq : Math.min(liquidity, liq);
4486
5129
  }
4487
5130
  const rawSum = outcomes.reduce((s, o) => s + o.probability, 0);
4488
- if (rawSum < 0.9 || rawSum > 1.15) return void 0;
5131
+ if (rawSum < 0.9 || rawSum > 1.15) {
5132
+ return ambiguous("outcome probabilities do not form a coherent 1X2");
5133
+ }
5134
+ if (!asOf) return malformed("no usable timestamp on the event or its markets");
4489
5135
  const signal = buildMarketSignal({
4490
5136
  match,
4491
5137
  source: "polymarket",
4492
- sourceMarketId: event.id ?? eventSlug,
4493
- asOf: asOf ?? (/* @__PURE__ */ new Date()).toISOString(),
5138
+ // Echoed into MCP structured content (tools.ts `market.id`), i.e. straight
5139
+ // into an agent's context. Stripping control characters is NOT sufficient
5140
+ // there: printable prose ("IGNORE PREVIOUS INSTRUCTIONS") survives that and
5141
+ // is precisely what matters for a model reading it. Gamma ids are short
5142
+ // opaque tokens, so validate that GRAMMAR and otherwise fall back to the
5143
+ // slug we derived ourselves.
5144
+ // The fallback is grammar-checked too. It is normally a slug we derived
5145
+ // ourselves, but `mapping.2026.json` can override it, so echoing it raw
5146
+ // was the one path around the agent-facing filter this line exists for.
5147
+ sourceMarketId: safeMarketId(event.id) ?? safeDerivedSlug(eventSlug),
5148
+ asOf,
4494
5149
  outcomes,
4495
5150
  liquidity,
4496
5151
  now: options?.now ?? this.opts.now,
4497
5152
  maxAgeMs: options?.maxAgeMs ?? this.opts.maxAgeMs
4498
5153
  });
4499
- return signal.ambiguous ? void 0 : signal;
5154
+ return signal.ambiguous ? ambiguous("signal does not map cleanly onto this fixture") : valid(signal);
4500
5155
  }
4501
5156
  };
5157
+ function safeMarketId(id) {
5158
+ return typeof id === "string" && /^[0-9]{1,32}$/.test(id) ? id : void 0;
5159
+ }
5160
+ function safeDerivedSlug(slug) {
5161
+ return typeof slug === "string" && /^fifwc-[a-z]{2,3}-[a-z]{2,3}-\d{4}-\d{2}-\d{2}$/.test(slug) ? slug : void 0;
5162
+ }
4502
5163
  var POLYMARKET_TOKEN = {
4503
5164
  SUI: "che",
4504
5165
  // Switzerland
@@ -4512,8 +5173,16 @@ var POLYMARKET_TOKEN = {
4512
5173
  // Croatia
4513
5174
  COD: "cdr",
4514
5175
  // DR Congo
4515
- CPV: "cvi"
5176
+ CPV: "cvi",
4516
5177
  // Cabo Verde
5178
+ // TWO letters, not three — the one entry that is not ISO alpha-3. Verified
5179
+ // live: `fifwc-kor-cze-2026-06-11` is a 404, `fifwc-kr-cze-2026-06-11`
5180
+ // resolves to "Korea Republic vs. Czechia". Korea's three group fixtures
5181
+ // therefore had no market line at all. The `^[a-z]{3}$` guard in
5182
+ // `deriveEventSlugs` validates the FIFA CODE, not the token, so a two-letter
5183
+ // alias passes through it unharmed.
5184
+ KOR: "kr"
5185
+ // Korea Republic
4517
5186
  };
4518
5187
  function pmTokens(code) {
4519
5188
  const c = code.toLowerCase();
@@ -4544,18 +5213,21 @@ function slugToken(m) {
4544
5213
  return (m.slug ?? "").toLowerCase().split("-").pop() ?? "";
4545
5214
  }
4546
5215
  function isDrawMarket(m) {
4547
- return slugToken(m) === "draw" || (m.groupItemTitle ?? "").trim().toLowerCase().startsWith("draw");
5216
+ const title = (m.groupItemTitle ?? "").trim().toLowerCase();
5217
+ return slugToken(m) === "draw" || title === "draw" || /^draw\s*\(/.test(title);
4548
5218
  }
4549
- function pickMarket(markets, teamCode, teamName) {
4550
- const tokens = pmTokens(teamCode);
5219
+ function pickMarket(markets, teamCode2, teamName) {
5220
+ const tokens = pmTokens(teamCode2);
4551
5221
  const name = teamName.trim().toLowerCase();
4552
5222
  const teamMarkets = markets.filter((m) => !isDrawMarket(m));
4553
- const bySlug = teamMarkets.find((m) => tokens.includes(slugToken(m)));
4554
- if (bySlug) return bySlug;
4555
- return teamMarkets.find((m) => (m.groupItemTitle ?? "").trim().toLowerCase() === name);
5223
+ const bySlug = teamMarkets.filter((m) => tokens.includes(slugToken(m)));
5224
+ if (bySlug.length > 1) return { kind: "ambiguous", count: bySlug.length };
5225
+ const byTitle = name ? teamMarkets.filter((m) => (m.groupItemTitle ?? "").trim().toLowerCase() === name) : [];
5226
+ if (byTitle.length > 1) return { kind: "ambiguous", count: byTitle.length };
5227
+ return selectOne([.../* @__PURE__ */ new Set([...bySlug, ...byTitle])]);
4556
5228
  }
4557
5229
  function pickDraw(markets) {
4558
- return markets.find(isDrawMarket);
5230
+ return selectOne(markets.filter(isDrawMarket));
4559
5231
  }
4560
5232
  function assertAllowedHost(base) {
4561
5233
  let host;
@@ -4569,20 +5241,29 @@ function assertAllowedHost(base) {
4569
5241
  }
4570
5242
  }
4571
5243
  function yesPrice(market) {
4572
- const labels = parseJsonArray(market.outcomes);
4573
- const prices = parseJsonArray(market.outcomePrices).map((p2) => Number(p2));
4574
- if (labels.length === 0 || labels.length !== prices.length) return void 0;
4575
- const i = labels.findIndex((l) => l.trim().toLowerCase() === "yes");
4576
- if (i < 0) return void 0;
4577
- const p = prices[i];
4578
- return typeof p === "number" && Number.isFinite(p) && p > 0 && p <= 1 ? p : void 0;
5244
+ const labels = parseJsonArray(market.outcomes).map((l) => l.trim().toLowerCase());
5245
+ const raw = parseJsonArray(market.outcomePrices);
5246
+ if (raw.some((v) => v.trim() === "" || !Number.isFinite(Number(v)))) return void 0;
5247
+ const prices = raw.map((v) => Number(v));
5248
+ if (labels.length !== 2 || prices.length !== 2) return void 0;
5249
+ const i = labels.indexOf("yes");
5250
+ const j = labels.indexOf("no");
5251
+ if (i < 0 || j < 0) return void 0;
5252
+ const yes = prices[i];
5253
+ const no = prices[j];
5254
+ if (![yes, no].every((v) => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 1)) {
5255
+ return void 0;
5256
+ }
5257
+ if (Math.abs(yes + no - 1) > 0.05) return void 0;
5258
+ return yes > 0 ? yes : void 0;
4579
5259
  }
4580
5260
  function parseJsonArray(v) {
4581
- if (Array.isArray(v)) return v.map((x) => String(x));
5261
+ const asText = (x) => typeof x === "string" || typeof x === "number" ? String(x) : "";
5262
+ if (Array.isArray(v)) return v.map(asText);
4582
5263
  if (typeof v === "string") {
4583
5264
  try {
4584
5265
  const parsed = JSON.parse(v);
4585
- return Array.isArray(parsed) ? parsed.map((x) => String(x)) : [];
5266
+ return Array.isArray(parsed) ? parsed.map(asText) : [];
4586
5267
  } catch {
4587
5268
  return [];
4588
5269
  }
@@ -4590,10 +5271,10 @@ function parseJsonArray(v) {
4590
5271
  return [];
4591
5272
  }
4592
5273
  function numberish(v) {
4593
- if (typeof v === "number") return Number.isFinite(v) ? v : void 0;
5274
+ if (typeof v === "number") return Number.isFinite(v) && v >= 0 ? v : void 0;
4594
5275
  if (typeof v === "string") {
4595
5276
  const n = Number(v);
4596
- return Number.isFinite(n) ? n : void 0;
5277
+ return Number.isFinite(n) && n >= 0 ? n : void 0;
4597
5278
  }
4598
5279
  return void 0;
4599
5280
  }
@@ -4620,7 +5301,7 @@ async function getMarketSignals(provider, matches, options) {
4620
5301
  try {
4621
5302
  return await provider.findSignals(matches, options);
4622
5303
  } catch {
4623
- return { signals: /* @__PURE__ */ new Map(), checked: /* @__PURE__ */ new Set() };
5304
+ return emptyBatch();
4624
5305
  }
4625
5306
  }
4626
5307
  var SHARE_HASHTAG = "#VibingLaVidaLoca";
@@ -4701,6 +5382,9 @@ function formatShareSnippet(input, options = {}) {
4701
5382
  if (input.degraded && input.matches.length > 0) {
4702
5383
  blocks.push("(Live data unavailable \u2014 showing the bundled schedule, not live scores.)");
4703
5384
  }
5385
+ if (includeMarkets && input.marketComplete === false) {
5386
+ blocks.push("(Market data unavailable or incomplete \u2014 not all fixtures were checked.)");
5387
+ }
4704
5388
  blocks.push(
4705
5389
  shareFooter({
4706
5390
  source: input.source,
@@ -4731,7 +5415,9 @@ function formatShareTable(input, options = {}) {
4731
5415
  const includeInstall = options.includeInstallLine !== false;
4732
5416
  const blocks = [];
4733
5417
  if (input.tables.length === 0) {
4734
- blocks.push(input.emptyNote ?? "No standings available.");
5418
+ blocks.push(
5419
+ input.emptyNote ?? (input.degraded ? "Live standings unavailable." : "No standings available.")
5420
+ );
4735
5421
  } else {
4736
5422
  for (const { group, rows } of input.tables) {
4737
5423
  blocks.push(
@@ -5015,6 +5701,7 @@ var EN2 = {
5015
5701
  "table.title": "Group {group}",
5016
5702
  "table.none": "No group found for {group}.",
5017
5703
  "table.degraded": "Live standings unavailable \u2014 showing the group roster.",
5704
+ "table.unavailable": "Live standings unavailable.",
5018
5705
  "table.empty": "No standings available.",
5019
5706
  "match.none": "No match found with id {id}.",
5020
5707
  "status.live": "LIVE",
@@ -5055,6 +5742,7 @@ var ES2 = {
5055
5742
  "table.title": "Grupo {group}",
5056
5743
  "table.none": "No se encontr\xF3 el grupo {group}.",
5057
5744
  "table.degraded": "Tabla en vivo no disponible \u2014 mostrando la lista del grupo.",
5745
+ "table.unavailable": "Tabla en vivo no disponible.",
5058
5746
  "table.empty": "No hay clasificaci\xF3n disponible.",
5059
5747
  "match.none": "No se encontr\xF3 partido con id {id}.",
5060
5748
  "status.live": "EN VIVO",
@@ -5095,6 +5783,7 @@ var PT2 = {
5095
5783
  "table.title": "Grupo {group}",
5096
5784
  "table.none": "Grupo {group} n\xE3o encontrado.",
5097
5785
  "table.degraded": "Classifica\xE7\xE3o ao vivo indispon\xEDvel \u2014 mostrando os times do grupo.",
5786
+ "table.unavailable": "Classifica\xE7\xE3o ao vivo indispon\xEDvel.",
5098
5787
  "table.empty": "Classifica\xE7\xE3o indispon\xEDvel.",
5099
5788
  "match.none": "Nenhum jogo encontrado com id {id}.",
5100
5789
  "status.live": "AO VIVO",
@@ -5135,6 +5824,7 @@ var FR2 = {
5135
5824
  "table.title": "Groupe {group}",
5136
5825
  "table.none": "Groupe {group} introuvable.",
5137
5826
  "table.degraded": "Classement en direct indisponible \u2014 affichage de la composition du groupe.",
5827
+ "table.unavailable": "Classement en direct indisponible.",
5138
5828
  "table.empty": "Aucun classement disponible.",
5139
5829
  "match.none": "Aucun match trouv\xE9 avec id {id}.",
5140
5830
  "status.live": "DIRECT",
@@ -5245,7 +5935,19 @@ function dataSource(source, lang, c) {
5245
5935
  }
5246
5936
 
5247
5937
  // src/marketCache.ts
5248
- import { readFileSync as readFileSync2 } from "fs";
5938
+ import { readFileSync as readFileSync3, statSync as statSync2 } from "fs";
5939
+ import { join as join3 } from "path";
5940
+
5941
+ // src/cache.ts
5942
+ import {
5943
+ closeSync as closeSync2,
5944
+ mkdirSync as mkdirSync2,
5945
+ openSync as openSync2,
5946
+ readFileSync as readFileSync2,
5947
+ rmSync,
5948
+ statSync,
5949
+ writeSync as writeSync2
5950
+ } from "fs";
5249
5951
  import { join as join2 } from "path";
5250
5952
 
5251
5953
  // src/paths.ts
@@ -5268,133 +5970,63 @@ function writeFileAtomic(path, data) {
5268
5970
  renameSync(tmp, path);
5269
5971
  }
5270
5972
 
5271
- // src/marketCache.ts
5272
- var POSITIVE_TTL_MS = 10 * 6e4;
5273
- var NEGATIVE_TTL_MS = 3 * 6e4;
5274
- function cachePath() {
5275
- return join2(cacheDir(), "market-signals.json");
5276
- }
5277
- function readFile() {
5278
- try {
5279
- return JSON.parse(readFileSync2(cachePath(), "utf8"));
5280
- } catch {
5281
- return void 0;
5282
- }
5283
- }
5284
- function readMarketCache(source, competition, now = Date.now()) {
5285
- const signals = /* @__PURE__ */ new Map();
5286
- const checked = /* @__PURE__ */ new Set();
5287
- const file = readFile();
5288
- if (!file || file.source !== source || file.competition !== competition) {
5289
- return { signals, checked };
5290
- }
5291
- for (const [id, entry] of Object.entries(file.entries ?? {})) {
5292
- const t2 = Date.parse(entry.fetchedAt);
5293
- if (!Number.isFinite(t2)) continue;
5294
- const ttl = entry.signal ? POSITIVE_TTL_MS : NEGATIVE_TTL_MS;
5295
- if (now - t2 > ttl) continue;
5296
- checked.add(id);
5297
- if (entry.signal) signals.set(id, entry.signal);
5298
- }
5299
- return { signals, checked };
5300
- }
5301
- function writeMarketCache(source, competition, attempted, fetched, now = Date.now()) {
5302
- if (attempted.length === 0) return;
5303
- try {
5304
- const existing = readFile();
5305
- const base = existing && existing.source === source && existing.competition === competition ? existing : { source, competition, entries: {} };
5306
- const fetchedAt = new Date(now).toISOString();
5307
- for (const id of attempted) {
5308
- base.entries[id] = { fetchedAt, signal: fetched.get(id) ?? null };
5309
- }
5310
- writeFileAtomic(cachePath(), JSON.stringify(base));
5311
- } catch {
5312
- }
5313
- }
5314
-
5315
- // src/starNudge.ts
5316
- import { readFileSync as readFileSync3 } from "fs";
5317
- import { join as join3 } from "path";
5318
- var REPO_URL = "https://github.com/arturogarrido/claudinho";
5319
- var NUDGE_EVERY = 5;
5320
- function counterPath() {
5321
- return join3(cacheDir(), "runs.json");
5322
- }
5323
- function shouldNudge(runCount, every = NUDGE_EVERY) {
5324
- return runCount > 0 && runCount % every === 0;
5325
- }
5326
- function bumpRunCount(path = counterPath()) {
5327
- try {
5328
- let count = 0;
5329
- try {
5330
- const raw = JSON.parse(readFileSync3(path, "utf8"));
5331
- if (typeof raw.count === "number" && Number.isFinite(raw.count)) count = raw.count;
5332
- } catch {
5333
- count = 0;
5334
- }
5335
- count += 1;
5336
- writeFileAtomic(path, JSON.stringify({ count }));
5337
- return count;
5338
- } catch {
5339
- return void 0;
5340
- }
5341
- }
5342
-
5343
- // src/clipboard.ts
5344
- import { spawnSync } from "child_process";
5345
- function clipboardTools(platform) {
5346
- if (platform === "darwin") return [{ cmd: "pbcopy", args: [] }];
5347
- if (platform === "win32") return [{ cmd: "clip", args: [] }];
5348
- return [
5349
- { cmd: "wl-copy", args: [] },
5350
- { cmd: "xclip", args: ["-selection", "clipboard"] },
5351
- { cmd: "xsel", args: ["--clipboard", "--input"] }
5352
- ];
5353
- }
5354
- function copyToClipboard(text, platform = process.platform) {
5355
- for (const { cmd, args } of clipboardTools(platform)) {
5356
- try {
5357
- const res = spawnSync(cmd, args, {
5358
- input: text,
5359
- stdio: ["pipe", "ignore", "ignore"],
5360
- timeout: 1e3
5361
- });
5362
- if (!res.error && res.status === 0) return true;
5363
- } catch {
5364
- }
5365
- }
5366
- return false;
5367
- }
5368
-
5369
5973
  // src/cache.ts
5370
- import {
5371
- closeSync as closeSync2,
5372
- mkdirSync as mkdirSync2,
5373
- openSync as openSync2,
5374
- readFileSync as readFileSync4,
5375
- rmSync,
5376
- statSync,
5377
- writeSync as writeSync2
5378
- } from "fs";
5379
- import { join as join4 } from "path";
5380
5974
  var CACHE_VERSION = 2;
5975
+ var MAX_STATE_BYTES = 1024 * 1024;
5976
+ var MAX_STATE_RECORDS = 1024;
5381
5977
  var LOCK_STALE_MS = 6e4;
5382
- function cachePath2(source = "espn", competition = DEFAULT_COMPETITION) {
5978
+ function cachePath(source = "espn", competition = DEFAULT_COMPETITION) {
5383
5979
  if (source === "espn" && competition === DEFAULT_COMPETITION) {
5384
- return join4(cacheDir(), "state.json");
5980
+ return join2(cacheDir(), "state.json");
5385
5981
  }
5386
5982
  const slug = `${source}.${competition}`.replace(/[^a-zA-Z0-9._-]/g, "_");
5387
- return join4(cacheDir(), `state.${slug}.json`);
5983
+ return join2(cacheDir(), `state.${slug}.json`);
5388
5984
  }
5389
5985
  function lockPath() {
5390
- return join4(cacheDir(), "refresh.lock");
5986
+ return join2(cacheDir(), "refresh.lock");
5987
+ }
5988
+ function validStamp(value) {
5989
+ if (typeof value !== "string") return false;
5990
+ const match = value.match(
5991
+ /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(?:\.(\d{1,3}))?Z$/
5992
+ );
5993
+ if (!match) return false;
5994
+ const parsed = Date.parse(value);
5995
+ if (!Number.isFinite(parsed)) return false;
5996
+ const canonical = `${match[1]}.${(match[2] ?? "").padEnd(3, "0")}Z`;
5997
+ return new Date(parsed).toISOString() === canonical;
5998
+ }
5999
+ function validScope(value) {
6000
+ return typeof value === "string" && value.length > 0 && value.length <= 128 && /^[a-zA-Z0-9._-]+$/.test(value);
6001
+ }
6002
+ function isCacheState(value) {
6003
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
6004
+ const s = value;
6005
+ if (s.version !== CACHE_VERSION) return false;
6006
+ if (!validStamp(s.updatedAt) || typeof s.degraded !== "boolean") return false;
6007
+ if (!validScope(s.source) || !validScope(s.competition)) return false;
6008
+ if (!Array.isArray(s.live) || s.live.length > MAX_STATE_RECORDS) return false;
6009
+ if (s.fixtures !== void 0 && (!Array.isArray(s.fixtures) || s.fixtures.length > MAX_STATE_RECORDS)) {
6010
+ return false;
6011
+ }
6012
+ for (const key of [
6013
+ "fixturesUpdatedAt",
6014
+ "fixturesAttemptedAt",
6015
+ "backoffUntil"
6016
+ ]) {
6017
+ if (s[key] !== void 0 && !validStamp(s[key])) return false;
6018
+ }
6019
+ return true;
5391
6020
  }
5392
6021
  function readState(source = "espn", competition = DEFAULT_COMPETITION) {
5393
6022
  try {
5394
- const s = JSON.parse(
5395
- readFileSync4(cachePath2(source, competition), "utf8")
5396
- );
5397
- return s.version === CACHE_VERSION ? s : void 0;
6023
+ const path = cachePath(source, competition);
6024
+ const info = statSync(path);
6025
+ if (!info.isFile() || info.size > MAX_STATE_BYTES) return void 0;
6026
+ const bytes = readFileSync2(path);
6027
+ if (bytes.byteLength > MAX_STATE_BYTES) return void 0;
6028
+ const parsed = JSON.parse(bytes.toString("utf8"));
6029
+ return isCacheState(parsed) ? parsed : void 0;
5398
6030
  } catch {
5399
6031
  return void 0;
5400
6032
  }
@@ -5405,41 +6037,45 @@ function readCurrentState(source, competition) {
5405
6037
  }
5406
6038
  function writeState(state) {
5407
6039
  writeFileAtomic(
5408
- cachePath2(state.source, state.competition),
6040
+ cachePath(state.source, state.competition),
5409
6041
  JSON.stringify({ ...state, version: CACHE_VERSION })
5410
6042
  );
5411
6043
  }
6044
+ var MAX_BACKOFF_MS = 30 * 6e4;
5412
6045
  function backoffActive(state, now = Date.now()) {
5413
6046
  if (!state?.backoffUntil) return false;
5414
6047
  const t2 = Date.parse(state.backoffUntil);
5415
- return Number.isFinite(t2) && now < t2;
6048
+ if (!Number.isFinite(t2)) return false;
6049
+ return now < t2 && t2 - now <= MAX_BACKOFF_MS;
5416
6050
  }
5417
6051
  function fixturesAttemptAgeMs(state, now = Date.now()) {
5418
- if (!state?.fixturesAttemptedAt) return Infinity;
5419
- const t2 = Date.parse(state.fixturesAttemptedAt);
5420
- return Number.isFinite(t2) ? now - t2 : Infinity;
6052
+ return stampAgeMs(state?.fixturesAttemptedAt, now);
6053
+ }
6054
+ var FUTURE_SKEW_MS2 = 6e4;
6055
+ function stampAgeMs(value, now) {
6056
+ if (!value) return Infinity;
6057
+ const t2 = Date.parse(value);
6058
+ if (!Number.isFinite(t2)) return Infinity;
6059
+ const age = now - t2;
6060
+ return age < -FUTURE_SKEW_MS2 ? Infinity : age;
5421
6061
  }
5422
6062
  function ageMs(state, now = Date.now()) {
5423
- if (!state) return Infinity;
5424
- const t2 = Date.parse(state.updatedAt);
5425
- return Number.isFinite(t2) ? now - t2 : Infinity;
6063
+ return stampAgeMs(state?.updatedAt, now);
5426
6064
  }
5427
6065
  function fixturesAgeMs(state, now = Date.now()) {
5428
- if (!state?.fixturesUpdatedAt) return Infinity;
5429
- const t2 = Date.parse(state.fixturesUpdatedAt);
5430
- return Number.isFinite(t2) ? now - t2 : Infinity;
6066
+ return stampAgeMs(state?.fixturesUpdatedAt, now);
5431
6067
  }
5432
6068
  function lockAgeMs(now = Date.now()) {
5433
6069
  const lp = lockPath();
5434
6070
  try {
5435
- const contents = readFileSync4(lp, "utf8");
6071
+ const contents = readFileSync2(lp, "utf8");
5436
6072
  const written = Number.parseInt(contents.split(/\s+/)[1] ?? "", 10);
5437
- if (Number.isFinite(written)) return now - written;
6073
+ if (Number.isFinite(written)) return stampAgeMs(new Date(written).toISOString(), now);
5438
6074
  } catch {
5439
6075
  return Infinity;
5440
6076
  }
5441
6077
  try {
5442
- return now - statSync(lp).mtimeMs;
6078
+ return stampAgeMs(new Date(statSync(lp).mtimeMs).toISOString(), now);
5443
6079
  } catch {
5444
6080
  return Infinity;
5445
6081
  }
@@ -5487,6 +6123,169 @@ function releaseLock() {
5487
6123
  }
5488
6124
  }
5489
6125
 
6126
+ // src/marketCache.ts
6127
+ var POSITIVE_TTL_MS = 10 * 6e4;
6128
+ var NEGATIVE_TTL_MS = 3 * 6e4;
6129
+ var MAX_MARKET_CACHE_BYTES = 1024 * 1024;
6130
+ var FUTURE_SKEW_MS3 = 6e4;
6131
+ function cachePath2() {
6132
+ return join3(cacheDir(), "market-signals.json");
6133
+ }
6134
+ function readFile() {
6135
+ try {
6136
+ const path = cachePath2();
6137
+ const info = statSync2(path);
6138
+ if (!info.isFile() || info.size > MAX_MARKET_CACHE_BYTES) return void 0;
6139
+ const bytes = readFileSync3(path);
6140
+ if (bytes.byteLength > MAX_MARKET_CACHE_BYTES) return void 0;
6141
+ const parsed = JSON.parse(bytes.toString("utf8"));
6142
+ if (!parsed || typeof parsed !== "object") return void 0;
6143
+ return parsed;
6144
+ } catch {
6145
+ return void 0;
6146
+ }
6147
+ }
6148
+ function isEntryShaped(e) {
6149
+ if (!e || typeof e !== "object") return false;
6150
+ const entry = e;
6151
+ if (typeof entry.fetchedAt !== "string" || !Number.isFinite(Date.parse(entry.fetchedAt))) {
6152
+ return false;
6153
+ }
6154
+ if (entry.signal === null) return true;
6155
+ return !!entry.signal && typeof entry.signal === "object";
6156
+ }
6157
+ function isUsableSignal(s) {
6158
+ if (!s) return false;
6159
+ if (s.source === "" || s.asOf === "" || s.outcomes.length === 0) return false;
6160
+ if (s.outcomes.some((o) => o.kind === "other")) return false;
6161
+ if (s.ambiguous) return false;
6162
+ if (!s.favorite) return false;
6163
+ return hasSaneDistribution(s.outcomes);
6164
+ }
6165
+ function readMarketCache(source, competition, now = Date.now()) {
6166
+ const signals = /* @__PURE__ */ new Map();
6167
+ const checked = /* @__PURE__ */ new Set();
6168
+ const file = readFile();
6169
+ if (!file || file.source !== source || file.competition !== competition) {
6170
+ return { signals, checked };
6171
+ }
6172
+ const entries = file.entries;
6173
+ if (!entries || typeof entries !== "object" || Array.isArray(entries)) {
6174
+ return { signals, checked };
6175
+ }
6176
+ let examined = 0;
6177
+ for (const id in entries) {
6178
+ if (!Object.hasOwn(entries, id)) continue;
6179
+ if (examined >= 256) break;
6180
+ examined += 1;
6181
+ const raw = entries[id];
6182
+ if (!isEntryShaped(raw)) continue;
6183
+ const entry = raw;
6184
+ const t2 = Date.parse(entry.fetchedAt);
6185
+ const ttl = entry.signal ? POSITIVE_TTL_MS : NEGATIVE_TTL_MS;
6186
+ const age = now - t2;
6187
+ if (age > ttl || age < -FUTURE_SKEW_MS3) continue;
6188
+ if (entry.signal === null) {
6189
+ checked.add(id);
6190
+ continue;
6191
+ }
6192
+ const sealed = parseCachedMarketSignal(entry.signal, { now: new Date(now) });
6193
+ if (sealed.kind !== "valid") continue;
6194
+ const clean = sealed.value;
6195
+ if (clean.matchId !== id || clean.source !== source) continue;
6196
+ if (!isUsableSignal(clean)) continue;
6197
+ checked.add(id);
6198
+ signals.set(id, clean);
6199
+ }
6200
+ return { signals, checked };
6201
+ }
6202
+ function writeMarketCache(source, competition, attempted, fetched, now = Date.now()) {
6203
+ if (attempted.length === 0) return;
6204
+ try {
6205
+ const existing = readFile();
6206
+ const reuse = existing && existing.source === source && existing.competition === competition;
6207
+ const carried = {};
6208
+ if (reuse && existing.entries && typeof existing.entries === "object" && !Array.isArray(existing.entries)) {
6209
+ let examined = 0;
6210
+ for (const id in existing.entries) {
6211
+ if (!Object.hasOwn(existing.entries, id)) continue;
6212
+ if (examined >= 256) break;
6213
+ examined += 1;
6214
+ const raw = existing.entries[id];
6215
+ if (!isEntryShaped(raw)) continue;
6216
+ const age = stampAgeMs(raw.fetchedAt, now);
6217
+ if (age > (raw.signal ? POSITIVE_TTL_MS : NEGATIVE_TTL_MS)) continue;
6218
+ if (age < -FUTURE_SKEW_MS3) continue;
6219
+ if (raw.signal !== null && !isUsableSignal(parsedValue(parseCachedMarketSignal(raw.signal, { now: new Date(now) })))) {
6220
+ continue;
6221
+ }
6222
+ carried[id] = raw;
6223
+ }
6224
+ }
6225
+ const base = { source, competition, entries: carried };
6226
+ const fetchedAt = new Date(now).toISOString();
6227
+ for (const id of attempted.slice(0, 256)) {
6228
+ base.entries[id] = { fetchedAt, signal: fetched.get(id) ?? null };
6229
+ }
6230
+ writeFileAtomic(cachePath2(), JSON.stringify(base));
6231
+ } catch {
6232
+ }
6233
+ }
6234
+
6235
+ // src/starNudge.ts
6236
+ import { readFileSync as readFileSync4 } from "fs";
6237
+ import { join as join4 } from "path";
6238
+ var REPO_URL = "https://github.com/arturogarrido/claudinho";
6239
+ var NUDGE_EVERY = 5;
6240
+ function counterPath() {
6241
+ return join4(cacheDir(), "runs.json");
6242
+ }
6243
+ function shouldNudge(runCount, every = NUDGE_EVERY) {
6244
+ return runCount > 0 && runCount % every === 0;
6245
+ }
6246
+ function bumpRunCount(path = counterPath()) {
6247
+ try {
6248
+ let count2 = 0;
6249
+ try {
6250
+ const raw = JSON.parse(readFileSync4(path, "utf8"));
6251
+ if (typeof raw.count === "number" && Number.isFinite(raw.count)) count2 = raw.count;
6252
+ } catch {
6253
+ count2 = 0;
6254
+ }
6255
+ count2 += 1;
6256
+ writeFileAtomic(path, JSON.stringify({ count: count2 }));
6257
+ return count2;
6258
+ } catch {
6259
+ return void 0;
6260
+ }
6261
+ }
6262
+
6263
+ // src/clipboard.ts
6264
+ import { spawnSync } from "child_process";
6265
+ function clipboardTools(platform) {
6266
+ if (platform === "darwin") return [{ cmd: "pbcopy", args: [] }];
6267
+ if (platform === "win32") return [{ cmd: "clip", args: [] }];
6268
+ return [
6269
+ { cmd: "wl-copy", args: [] },
6270
+ { cmd: "xclip", args: ["-selection", "clipboard"] },
6271
+ { cmd: "xsel", args: ["--clipboard", "--input"] }
6272
+ ];
6273
+ }
6274
+ function copyToClipboard(text, platform = process.platform) {
6275
+ for (const { cmd, args } of clipboardTools(platform)) {
6276
+ try {
6277
+ const res = spawnSync(cmd, args, {
6278
+ input: text,
6279
+ stdio: ["pipe", "ignore", "ignore"],
6280
+ timeout: 1e3
6281
+ });
6282
+ if (!res.error && res.status === 0) return true;
6283
+ } catch {
6284
+ }
6285
+ }
6286
+ return false;
6287
+ }
6288
+
5490
6289
  // src/statusline.ts
5491
6290
  var DISPLAY_STALE_MS = 5 * 6e4;
5492
6291
  var TOURNAMENT_COMPLETE_LINE = "\u26BD World Cup 2026 is complete \xB7 Thanks for vibing with Claudinho";
@@ -5523,36 +6322,110 @@ function matchSegment(m, compact, flags) {
5523
6322
  const away = compact ? m.away.flag : `${m.away.code} ${m.away.flag}`;
5524
6323
  return `${home} ${scoreline(m)} ${away} ${minute}`;
5525
6324
  }
6325
+ var MAX_LIVE_CONSIDERED = 64;
6326
+ var MAX_LIVE_EXAMINED = 512;
6327
+ function sealFixtures(raw) {
6328
+ if (raw === void 0) {
6329
+ return { items: [], total: 0, shown: 0, truncated: false, complete: true };
6330
+ }
6331
+ if (!Array.isArray(raw)) {
6332
+ return { items: [], total: 0, shown: 0, truncated: false, complete: false };
6333
+ }
6334
+ const out2 = [];
6335
+ let inspected = 0;
6336
+ let readable = true;
6337
+ for (const rec of raw) {
6338
+ if (out2.length >= MAX_LIVE_CONSIDERED || inspected >= MAX_LIVE_EXAMINED) break;
6339
+ inspected++;
6340
+ if (!isMatchShaped(rec)) {
6341
+ readable = false;
6342
+ continue;
6343
+ }
6344
+ const sealed = parsedValue(parseCachedMatch(rec, { events: false }));
6345
+ if (sealed) out2.push(sealed);
6346
+ else readable = false;
6347
+ }
6348
+ const exhausted = inspected === raw.length;
6349
+ const complete = exhausted && readable;
6350
+ return {
6351
+ items: out2,
6352
+ // Exact only when complete; otherwise this is the number actually sealed.
6353
+ // Callers use a nonnumeric marker for an incomplete scan.
6354
+ total: out2.length,
6355
+ shown: out2.length,
6356
+ truncated: !exhausted,
6357
+ complete
6358
+ };
6359
+ }
5526
6360
  function liveMatchesFromCache(state, nowMs = Date.now()) {
5527
6361
  const fresh = state && ageMs(state, nowMs) < DISPLAY_STALE_MS;
5528
- const liveArr = fresh && Array.isArray(state?.live) ? state.live : [];
5529
- return liveArr.filter(
5530
- (m) => !!m && typeof m === "object" && isLive(m.status) && !!m.home?.code && !!m.away?.code
5531
- ).map(sanitizeMatchStrings);
6362
+ const rawLive = fresh ? state?.live : [];
6363
+ if (!Array.isArray(rawLive)) {
6364
+ return { items: [], total: 0, shown: 0, truncated: false, complete: false };
6365
+ }
6366
+ const liveArr = rawLive;
6367
+ const out2 = [];
6368
+ let inspected = 0;
6369
+ let readable = true;
6370
+ for (let i = 0; i < liveArr.length; i++) {
6371
+ if (out2.length >= MAX_LIVE_CONSIDERED || inspected >= MAX_LIVE_EXAMINED) break;
6372
+ inspected++;
6373
+ const raw = liveArr[i];
6374
+ if (!raw || typeof raw !== "object") {
6375
+ readable = false;
6376
+ continue;
6377
+ }
6378
+ const m = raw;
6379
+ if (!isLive(m.status) || !m.home?.code || !m.away?.code) {
6380
+ readable = false;
6381
+ continue;
6382
+ }
6383
+ const sealed = parsedValue(parseCachedMatch(m, { events: false }));
6384
+ if (sealed) out2.push(sealed);
6385
+ else readable = false;
6386
+ }
6387
+ const exhausted = inspected === liveArr.length;
6388
+ const complete = exhausted && readable;
6389
+ return {
6390
+ items: out2,
6391
+ total: out2.length,
6392
+ shown: out2.length,
6393
+ truncated: !exhausted,
6394
+ // False when we stopped early or a record was unreadable. In either case an
6395
+ // empty list must not render as the authoritative "nothing is on".
6396
+ complete
6397
+ };
5532
6398
  }
6399
+ var DEFAULT_MAX_SEGMENTS = 8;
6400
+ var MAX_LINE_COLUMNS = 200;
5533
6401
  function renderPrompt(state, opts = {}) {
6402
+ return truncateVisible(renderPromptLine(state, opts), MAX_LINE_COLUMNS);
6403
+ }
6404
+ function renderPromptLine(state, opts = {}) {
5534
6405
  const now = opts.now ?? /* @__PURE__ */ new Date();
5535
6406
  const nowMs = now.getTime();
5536
6407
  const defaultCompetition = opts.defaultCompetition ?? true;
5537
6408
  const compact = opts.compact ?? true;
5538
6409
  const flags = opts.flags ?? true;
5539
6410
  const team = opts.team?.toUpperCase();
5540
- const live = liveMatchesFromCache(state, nowMs);
5541
- const cachedFixtures = Array.isArray(state?.fixtures) ? state.fixtures.filter(isMatchShaped).map(sanitizeMatchStrings) : [];
6411
+ const liveList = liveMatchesFromCache(state, nowMs);
6412
+ const live = liveList.items;
6413
+ const cachedFixtureList = sealFixtures(state?.fixtures);
6414
+ const cachedFixtures = [...cachedFixtureList.items];
5542
6415
  const schedule = cachedFixtures.length ? mergeLive(allFixtures(), cachedFixtures) : void 0;
5543
6416
  if (team) {
5544
6417
  const mine = live.find((m) => m.home?.code === team || m.away?.code === team);
5545
6418
  if (mine) return `\u26BD ${matchSegment(mine, compact, flags)}`;
5546
6419
  } else if (live.length > 0) {
5547
- const max = opts.max && opts.max > 0 ? opts.max : live.length;
6420
+ const max = opts.max && opts.max > 0 ? Math.min(opts.max, DEFAULT_MAX_SEGMENTS) : DEFAULT_MAX_SEGMENTS;
5548
6421
  const shown = live.slice(0, max);
5549
- let line2 = "\u26BD " + shown.map((m) => matchSegment(m, compact, flags)).join(" \xB7 ");
5550
6422
  const overflow = live.length - shown.length;
5551
- if (overflow > 0) line2 += ` +${overflow}`;
5552
- return line2;
6423
+ const marker = !liveList.complete ? " +more" : overflow > 0 ? ` +${overflow}` : "";
6424
+ const body = "\u26BD " + shown.map((m) => matchSegment(m, compact, flags)).join(" \xB7 ");
6425
+ return truncateVisible(body, MAX_LINE_COLUMNS - displayWidth(marker)) + marker;
5553
6426
  }
5554
6427
  const cacheFresh = !!state && state.degraded !== true && ageMs(state, nowMs) < DISPLAY_STALE_MS;
5555
- if (!cacheFresh) {
6428
+ if (!cacheFresh || !liveList.complete) {
5556
6429
  const win = fixturesInLiveWindow(nowMs, schedule).filter(
5557
6430
  (m) => !team || m.home.code === team || m.away.code === team
5558
6431
  );
@@ -5574,6 +6447,15 @@ function renderPrompt(state, opts = {}) {
5574
6447
  }
5575
6448
 
5576
6449
  // src/hook.ts
6450
+ var MAX_HOOK_MATCHES = 12;
6451
+ var MAX_HOOK_CODE_POINTS = 4096;
6452
+ function boundContext(text, marker = "") {
6453
+ const points = [...text];
6454
+ if (points.length + [...marker].length <= MAX_HOOK_CODE_POINTS) return text + marker;
6455
+ const room = Math.max(0, MAX_HOOK_CODE_POINTS - [...marker].length);
6456
+ return `${points.slice(0, room).join("")}
6457
+ (context truncated)${marker}`;
6458
+ }
5577
6459
  function rosterPinned(t2) {
5578
6460
  const { team } = lookupTeam(t2.code);
5579
6461
  return team ? { ...t2, name: team.name, flag: team.flag } : t2;
@@ -5590,7 +6472,8 @@ function renderHook(state, opts = {}) {
5590
6472
  const now = opts.now ?? /* @__PURE__ */ new Date();
5591
6473
  const team = opts.team?.toUpperCase();
5592
6474
  const flags = opts.flags ?? true;
5593
- let live = liveMatchesFromCache(state, now.getTime());
6475
+ const liveList = liveMatchesFromCache(state, now.getTime());
6476
+ let live = [...liveList.items];
5594
6477
  if (live.length === 0) return "";
5595
6478
  if (team) {
5596
6479
  live = [...live].sort((a, b) => {
@@ -5599,9 +6482,13 @@ function renderHook(state, opts = {}) {
5599
6482
  return aHas - bHas;
5600
6483
  });
5601
6484
  }
5602
- const lines = live.map((mm) => line(mm, flags)).join("\n");
5603
- return `[Claudinho \u2014 live football scores right now]
5604
- ${lines}`;
6485
+ const shown = live.slice(0, MAX_HOOK_MATCHES);
6486
+ const overflow = live.length - shown.length;
6487
+ const lines = shown.map((mm) => line(mm, flags)).join("\n");
6488
+ const more = !liveList.complete ? "\n(more live matches may not be shown)" : overflow > 0 ? `
6489
+ (+${overflow} more not shown)` : "";
6490
+ return boundContext(`[Claudinho \u2014 live football scores right now]
6491
+ ${lines}`, more);
5605
6492
  }
5606
6493
 
5607
6494
  // src/refresh.ts
@@ -5890,11 +6777,15 @@ function adapterFor({ cfg, adapter }) {
5890
6777
  }
5891
6778
  var DEFAULT_ON_MARKET_OPTS = { deadlineMs: 2e3, timeoutMs: 2500 };
5892
6779
  var MARKETS_CMD_OPTS = { deadlineMs: 12e3, timeoutMs: 6e3 };
5893
- async function marketSignalsFor(ctx, matches, opts = {}) {
5894
- if (ctx.marketProvider) return (await getMarketSignals(ctx.marketProvider, matches, opts)).signals;
6780
+ async function marketSignalsFor(ctx, matches, opts = {}, providerFactory = makeMarketProvider) {
6781
+ if (ctx.marketProvider) {
6782
+ const b = await getMarketSignals(ctx.marketProvider, matches, opts);
6783
+ return { signals: resolvedValues(b), complete: b.complete };
6784
+ }
5895
6785
  const source = resolveMarketSource();
5896
6786
  if (source !== "polymarket") {
5897
- return (await getMarketSignals(makeMarketProvider(source), matches, opts)).signals;
6787
+ const b = await getMarketSignals(providerFactory(source), matches, opts);
6788
+ return { signals: resolvedValues(b), complete: b.complete };
5898
6789
  }
5899
6790
  const competition = resolveCompetition();
5900
6791
  const { signals: cached, checked: cachedIds } = readMarketCache("polymarket", competition);
@@ -5902,35 +6793,39 @@ async function marketSignalsFor(ctx, matches, opts = {}) {
5902
6793
  const miss = [];
5903
6794
  for (const m of matches) {
5904
6795
  const hit = cached.get(m.id);
5905
- if (hit) result.set(m.id, hit);
5906
- else if (!cachedIds.has(m.id)) miss.push(m);
6796
+ if (hit && marketSignalRendersFor(m, hit)) {
6797
+ result.set(m.id, hit);
6798
+ continue;
6799
+ }
6800
+ if (!hit && cachedIds.has(m.id)) continue;
6801
+ miss.push(m);
5907
6802
  }
6803
+ let complete = true;
5908
6804
  if (miss.length > 0) {
5909
- const { signals: fetched, checked } = await getMarketSignals(
5910
- makeMarketProvider("polymarket"),
5911
- miss,
5912
- opts
5913
- );
5914
- writeMarketCache("polymarket", competition, [...checked], fetched);
6805
+ const batch = await getMarketSignals(providerFactory("polymarket"), miss, opts);
6806
+ const fetched = resolvedValues(batch);
6807
+ complete = batch.complete;
6808
+ writeMarketCache("polymarket", competition, [...cacheableKeys(batch)], fetched);
5915
6809
  for (const [id, s] of fetched) result.set(id, s);
5916
6810
  }
5917
- return result;
6811
+ return { signals: result, complete };
5918
6812
  }
5919
6813
  async function reliableMarketSignals(ctx, matches) {
5920
- if (ctx.cfg.markets === false) return /* @__PURE__ */ new Map();
6814
+ if (ctx.cfg.markets === false) return { signals: /* @__PURE__ */ new Map(), complete: true };
5921
6815
  const now = ctx.now ?? /* @__PURE__ */ new Date();
5922
6816
  const relevant = matches.filter((m) => marketRelevant(m, now));
5923
- if (relevant.length === 0) return /* @__PURE__ */ new Map();
6817
+ if (relevant.length === 0) return { signals: /* @__PURE__ */ new Map(), complete: true };
5924
6818
  const raw = await marketSignalsFor(ctx, relevant, DEFAULT_ON_MARKET_OPTS);
5925
6819
  const out2 = /* @__PURE__ */ new Map();
5926
- for (const [id, s] of raw) {
6820
+ for (const [id, s] of raw.signals) {
5927
6821
  const m = relevant.find((x) => x.id === id);
5928
6822
  if (m && isReliableMarketSignal(s, { now }) && marketSignalRendersFor(m, s)) out2.set(id, s);
5929
6823
  }
5930
- return out2;
6824
+ return { signals: out2, complete: raw.complete };
5931
6825
  }
5932
6826
  async function reliableMarketSignalFor(ctx, match) {
5933
- return (await reliableMarketSignals(ctx, [match])).get(match.id);
6827
+ const result = await reliableMarketSignals(ctx, [match]);
6828
+ return { signal: result.signals.get(match.id), complete: result.complete };
5934
6829
  }
5935
6830
  function out(line2 = "") {
5936
6831
  process.stdout.write(line2 + "\n");
@@ -5989,14 +6884,15 @@ async function cmdToday(date, ctx) {
5989
6884
  const targetDate = date ?? localDate((/* @__PURE__ */ new Date()).toISOString(), cfg.tz);
5990
6885
  const { matches, degraded, source } = await getMatchesForDate(adapter, targetDate);
5991
6886
  const todays = fixturesByDate(targetDate, matches, cfg.tz);
5992
- const signals = await reliableMarketSignals(ctx, todays);
6887
+ const market = await reliableMarketSignals(ctx, todays);
5993
6888
  if (cfg.json) {
5994
6889
  emitJson({
5995
6890
  date: targetDate,
5996
6891
  degraded,
5997
6892
  source: source ?? null,
5998
6893
  matches: todays,
5999
- marketSignals: Object.fromEntries(signals)
6894
+ marketComplete: market.complete,
6895
+ marketSignals: Object.fromEntries(market.signals)
6000
6896
  });
6001
6897
  return;
6002
6898
  }
@@ -6011,10 +6907,13 @@ async function cmdToday(date, ctx) {
6011
6907
  } else {
6012
6908
  for (const m of todays) {
6013
6909
  out(matchLine(m, cfg, t2, c, flags));
6014
- const s = signals.get(m.id);
6910
+ const s = market.signals.get(m.id);
6015
6911
  if (s) out(" " + c.dim(marketLine(s, m)));
6016
6912
  }
6017
6913
  }
6914
+ if (!market.complete) {
6915
+ out(c.dim(" Market data unavailable or incomplete \u2014 not all fixtures were checked."));
6916
+ }
6018
6917
  out();
6019
6918
  if (degraded) out(c.dim(" " + t2("feed.degraded")));
6020
6919
  const src = dataSource(source, cfg.lang, c);
@@ -6099,9 +6998,9 @@ function cmdTeam(query, ctx) {
6099
6998
  const c = painterFor(cfg);
6100
6999
  const flags = flagsEnabled();
6101
7000
  const label = (tm) => {
6102
- const flag = flags ? `${tm.flag} ` : "";
7001
+ const flag2 = flags ? `${tm.flag} ` : "";
6103
7002
  const grp = tm.group ? ` \xB7 ${t2("team.group", { group: tm.group })}` : "";
6104
- return ` ${flag}${c.bold(tm.name)} ${c.dim(tm.code + grp)}`;
7003
+ return ` ${flag2}${c.bold(tm.name)} ${c.dim(tm.code + grp)}`;
6105
7004
  };
6106
7005
  out();
6107
7006
  if (!q) {
@@ -6137,11 +7036,12 @@ async function cmdTable(group, ctx) {
6137
7036
  out();
6138
7037
  out(
6139
7038
  c.dim(
6140
- " " + (group ? t2("table.none", { group: group.toUpperCase() }) : t2("table.empty"))
7039
+ " " + (degraded ? t2("table.unavailable") : group ? t2("table.none", { group: group.toUpperCase() }) : t2("table.empty"))
6141
7040
  )
6142
7041
  );
6143
7042
  out();
6144
- if (degraded) out(c.dim(" " + t2("table.degraded")));
7043
+ const src2 = dataSource(source, cfg.lang, c);
7044
+ if (src2) out(src2);
6145
7045
  out(disclaimer(t2, c));
6146
7046
  return;
6147
7047
  }
@@ -6352,13 +7252,14 @@ async function cmdMatch(id, ctx) {
6352
7252
  const { cfg, t: t2 } = ctx;
6353
7253
  precheck(cfg, t2);
6354
7254
  const { match, degraded, source: liveSource } = await getMatchById(adapterFor(ctx), id);
6355
- const marketSignal = match ? await reliableMarketSignalFor(ctx, match) : void 0;
7255
+ const market = match ? await reliableMarketSignalFor(ctx, match) : { signal: void 0, complete: true };
6356
7256
  if (cfg.json) {
6357
7257
  emitJson({
6358
7258
  degraded,
6359
7259
  match: match ?? null,
6360
7260
  source: liveSource ?? null,
6361
- marketSignal: marketSignal ?? null
7261
+ marketComplete: market.complete,
7262
+ marketSignal: market.signal ?? null
6362
7263
  });
6363
7264
  return;
6364
7265
  }
@@ -6386,9 +7287,13 @@ async function cmdMatch(id, ctx) {
6386
7287
  out(` ${e.minute}' ${e.type} ${e.teamCode}${e.player ? ` \u2014 ${e.player}` : ""}`);
6387
7288
  }
6388
7289
  }
6389
- if (marketSignal) {
7290
+ if (market.signal) {
7291
+ out();
7292
+ for (const mline of marketBlock(market.signal, match)) out(" " + c.dim(mline));
7293
+ }
7294
+ if (!market.complete) {
6390
7295
  out();
6391
- for (const mline of marketBlock(marketSignal, match)) out(" " + c.dim(mline));
7296
+ out(c.dim(" Market data unavailable or incomplete \u2014 this match could not be checked."));
6392
7297
  }
6393
7298
  out();
6394
7299
  if (degraded) out(c.dim(" " + t2("feed.degraded")));
@@ -6419,14 +7324,16 @@ async function cmdMarkets(target, team, ctx) {
6419
7324
  const code = resolveTeamArg(team, "Usage: claudinho markets next <team> (or set CLAUDINHO_TEAM)", t2);
6420
7325
  const now2 = ctx.now ?? /* @__PURE__ */ new Date();
6421
7326
  const { match: fixture, degraded } = await marketFixtureForTeam(adapterFor(ctx), code, now2);
6422
- const sig = fixture && marketRelevant(fixture, now2) ? (await marketSignalsFor(ctx, [fixture], MARKETS_CMD_OPTS)).get(fixture.id) : void 0;
6423
- const shown = fixture && sig && marketDisplayable(fixture, sig) ? sig : void 0;
7327
+ const market = fixture && marketRelevant(fixture, now2) ? await marketSignalsFor(ctx, [fixture], MARKETS_CMD_OPTS) : { signals: /* @__PURE__ */ new Map(), complete: true };
7328
+ const sig = fixture ? market.signals.get(fixture.id) : void 0;
7329
+ const shown = market.complete && fixture && sig && marketDisplayable(fixture, sig) ? sig : void 0;
6424
7330
  if (cfg.json) {
6425
7331
  emitJson({
6426
7332
  team: code,
6427
7333
  matchId: fixture?.id ?? null,
6428
7334
  degraded,
6429
7335
  informationalOnly: true,
7336
+ complete: market.complete,
6430
7337
  signal: shown ?? null
6431
7338
  });
6432
7339
  return;
@@ -6439,7 +7346,9 @@ async function cmdMarkets(target, team, ctx) {
6439
7346
  out(header(marketHeaderLine(fixture, cfg), c2));
6440
7347
  out();
6441
7348
  if (shown) printMarketBlock(fixture, shown, c2);
6442
- else out(c2.dim(" " + noSignalLine(fixture, now2)));
7349
+ else if (!market.complete) {
7350
+ out(c2.dim(" Market data unavailable or incomplete \u2014 this match could not be checked."));
7351
+ } else out(c2.dim(" " + noSignalLine(fixture, now2)));
6443
7352
  }
6444
7353
  out();
6445
7354
  out(disclaimer(t2, c2));
@@ -6450,10 +7359,16 @@ async function cmdMarkets(target, team, ctx) {
6450
7359
  precheck(cfg, t2);
6451
7360
  const now2 = ctx.now ?? /* @__PURE__ */ new Date();
6452
7361
  const { match } = await getMatchById(adapterFor(ctx), target);
6453
- const sig = match && marketRelevant(match, now2) ? (await marketSignalsFor(ctx, [match], MARKETS_CMD_OPTS)).get(match.id) : void 0;
6454
- const shown = match && sig && marketDisplayable(match, sig) ? sig : void 0;
7362
+ const market = match && marketRelevant(match, now2) ? await marketSignalsFor(ctx, [match], MARKETS_CMD_OPTS) : { signals: /* @__PURE__ */ new Map(), complete: true };
7363
+ const sig = match ? market.signals.get(match.id) : void 0;
7364
+ const shown = market.complete && match && sig && marketDisplayable(match, sig) ? sig : void 0;
6455
7365
  if (cfg.json) {
6456
- emitJson({ matchId: target, informationalOnly: true, signal: shown ?? null });
7366
+ emitJson({
7367
+ matchId: target,
7368
+ informationalOnly: true,
7369
+ complete: market.complete,
7370
+ signal: shown ?? null
7371
+ });
6457
7372
  return;
6458
7373
  }
6459
7374
  const c2 = painterFor(cfg);
@@ -6464,7 +7379,9 @@ async function cmdMarkets(target, team, ctx) {
6464
7379
  out(header(marketHeaderLine(match, cfg), c2));
6465
7380
  out();
6466
7381
  if (shown) printMarketBlock(match, shown, c2);
6467
- else out(c2.dim(" " + noSignalLine(match, now2)));
7382
+ else if (!market.complete) {
7383
+ out(c2.dim(" Market data unavailable or incomplete \u2014 this match could not be checked."));
7384
+ } else out(c2.dim(" " + noSignalLine(match, now2)));
6468
7385
  }
6469
7386
  out();
6470
7387
  out(disclaimer(t2, c2));
@@ -6478,14 +7395,14 @@ async function cmdMarkets(target, team, ctx) {
6478
7395
  const { matches } = await getMatchesForDate(adapterFor(ctx), date);
6479
7396
  const todays = fixturesByDate(date, matches, cfg.tz);
6480
7397
  const relevant = todays.filter((m) => marketRelevant(m, now));
6481
- const signals = await marketSignalsFor(ctx, relevant, MARKETS_CMD_OPTS);
7398
+ const { signals, complete } = await marketSignalsFor(ctx, relevant, MARKETS_CMD_OPTS);
6482
7399
  const rows = relevant.map((m) => ({ match: m, signal: signals.get(m.id) })).filter(
6483
7400
  (r) => !!r.signal && marketDisplayable(r.match, r.signal)
6484
7401
  );
6485
7402
  if (cfg.json) {
6486
7403
  const marketSignals = {};
6487
7404
  for (const r of rows) marketSignals[r.match.id] = r.signal;
6488
- emitJson({ date, informationalOnly: true, marketSignals });
7405
+ emitJson({ date, informationalOnly: true, complete, marketSignals });
6489
7406
  return;
6490
7407
  }
6491
7408
  const c = painterFor(cfg);
@@ -6493,13 +7410,21 @@ async function cmdMarkets(target, team, ctx) {
6493
7410
  out(header(`Market signals \xB7 ${date}`, c));
6494
7411
  out();
6495
7412
  if (rows.length === 0) {
6496
- out(c.dim(` No market signals available for ${date}.`));
7413
+ out(
7414
+ c.dim(
7415
+ complete ? ` No market signals available for ${date}.` : ` Market data unavailable or incomplete for ${date} \u2014 not all fixtures could be checked.`
7416
+ )
7417
+ );
6497
7418
  } else {
6498
7419
  for (const { match, signal } of rows) {
6499
7420
  out(" " + c.bold(marketHeaderLine(match, cfg)));
6500
7421
  printMarketBlock(match, signal, c);
6501
7422
  out();
6502
7423
  }
7424
+ if (!complete) {
7425
+ out(c.dim(` Market data unavailable or incomplete for ${date} \u2014 not all fixtures could be checked.`));
7426
+ out();
7427
+ }
6503
7428
  }
6504
7429
  out(disclaimer(t2, c));
6505
7430
  out(c.dim(MARKET_INFO));
@@ -6510,11 +7435,11 @@ function pickShareStyle(v) {
6510
7435
  async function reliableShareSignals(ctx, matches) {
6511
7436
  const raw = await reliableMarketSignals(ctx, matches);
6512
7437
  const out2 = /* @__PURE__ */ new Map();
6513
- for (const [id, s] of raw) {
7438
+ for (const [id, s] of raw.signals) {
6514
7439
  const m = matches.find((x) => x.id === id);
6515
7440
  if (m && marketDisplayable(m, s)) out2.set(id, s);
6516
7441
  }
6517
- return out2;
7442
+ return { signals: out2, complete: raw.complete };
6518
7443
  }
6519
7444
  function emitShare(ctx, e, copy) {
6520
7445
  const snippet = formatShareSnippet(e.input, e.options);
@@ -6529,6 +7454,7 @@ function emitShare(ctx, e, copy) {
6529
7454
  style: e.options.style ?? "social",
6530
7455
  snippet,
6531
7456
  matches: e.input.matches,
7457
+ marketComplete: e.input.marketComplete ?? true,
6532
7458
  marketSignals: Object.fromEntries(e.input.marketSignals ?? /* @__PURE__ */ new Map())
6533
7459
  });
6534
7460
  } else {
@@ -6648,11 +7574,12 @@ async function cmdShare(target, team, opts, ctx) {
6648
7574
  {
6649
7575
  group,
6650
7576
  tables,
6651
- // Degraded ⇒ a static roster, served by no live provider: no attribution.
7577
+ // Degraded ⇒ no live provider: no attribution. Open-scope outages
7578
+ // have no compatible bundled roster, so name the outage in the empty card.
6652
7579
  source: degraded2 ? void 0 : source2,
6653
7580
  degraded: degraded2,
6654
7581
  installLine: group ? `npx @claudinho/cli table ${group}` : "npx @claudinho/cli table",
6655
- emptyNote: group ? `No group ${group}.` : "No standings available.",
7582
+ emptyNote: degraded2 ? "Live standings unavailable." : group ? `No group ${group}.` : "No standings available.",
6656
7583
  options: baseOptions
6657
7584
  },
6658
7585
  copy
@@ -6699,7 +7626,7 @@ async function cmdShare(target, team, opts, ctx) {
6699
7626
  ctx.now ?? /* @__PURE__ */ new Date()
6700
7627
  );
6701
7628
  const matches = fixture ? [fixture] : [];
6702
- const signals2 = await reliableShareSignals(ctx, matches);
7629
+ const market2 = await reliableShareSignals(ctx, matches);
6703
7630
  const teamName = fixture ? fixture.home.code === code ? fixture.home.name : fixture.away.name : code;
6704
7631
  emitShare(
6705
7632
  ctx,
@@ -6710,7 +7637,8 @@ async function cmdShare(target, team, opts, ctx) {
6710
7637
  input: {
6711
7638
  title: `Next up for ${teamName}`,
6712
7639
  matches,
6713
- marketSignals: signals2,
7640
+ marketSignals: market2.signals,
7641
+ marketComplete: market2.complete,
6714
7642
  // Attribute the provider when the overlay resolved the tie (knockout);
6715
7643
  // undefined for a static group fixture — parity with CLI `next`.
6716
7644
  source: source2,
@@ -6731,7 +7659,7 @@ async function cmdShare(target, team, opts, ctx) {
6731
7659
  precheck(cfg, t2);
6732
7660
  const { match, degraded: degraded2, source: source2 } = await getMatchById(adapterFor(ctx), target);
6733
7661
  const matches = match ? [match] : [];
6734
- const signals2 = await reliableShareSignals(ctx, matches);
7662
+ const market2 = await reliableShareSignals(ctx, matches);
6735
7663
  emitShare(
6736
7664
  ctx,
6737
7665
  {
@@ -6740,7 +7668,8 @@ async function cmdShare(target, team, opts, ctx) {
6740
7668
  input: {
6741
7669
  title: "Match pulse",
6742
7670
  matches,
6743
- marketSignals: signals2,
7671
+ marketSignals: market2.signals,
7672
+ marketComplete: market2.complete,
6744
7673
  source: source2,
6745
7674
  degraded: degraded2,
6746
7675
  emptyNote: `No match found with id ${target}.`,
@@ -6759,7 +7688,7 @@ async function cmdShare(target, team, opts, ctx) {
6759
7688
  const date = explicitDate ?? localDate((/* @__PURE__ */ new Date()).toISOString(), cfg.tz);
6760
7689
  const { matches: all, degraded, source } = await getMatchesForDate(adapterFor(ctx), date);
6761
7690
  const todays = fixturesByDate(date, all, cfg.tz);
6762
- const signals = await reliableShareSignals(ctx, todays);
7691
+ const market = await reliableShareSignals(ctx, todays);
6763
7692
  const human = formatDate(`${date}T12:00:00.000Z`, { tz: cfg.tz, locale: cfg.lang });
6764
7693
  const title = explicitDate ? `Matches \xB7 ${human}` : `Today's matches \xB7 ${human}`;
6765
7694
  emitShare(
@@ -6770,7 +7699,8 @@ async function cmdShare(target, team, opts, ctx) {
6770
7699
  input: {
6771
7700
  title,
6772
7701
  matches: todays,
6773
- marketSignals: signals,
7702
+ marketSignals: market.signals,
7703
+ marketComplete: market.complete,
6774
7704
  source,
6775
7705
  degraded,
6776
7706
  emptyNote: `No matches scheduled for ${human}.`,
@@ -6873,7 +7803,7 @@ function cmdVibe(ctx) {
6873
7803
  try {
6874
7804
  const state = readCurrentState(cfg.source, resolveCompetition());
6875
7805
  liveSeg = vibeLiveSegment(
6876
- liveMatchesFromCache(state, (ctx.now ?? /* @__PURE__ */ new Date()).getTime()),
7806
+ liveMatchesFromCache(state, (ctx.now ?? /* @__PURE__ */ new Date()).getTime()).items,
6877
7807
  // Name-or-code, matching the statusline/hook (offline lookup).
6878
7808
  resolveEnvTeam(process.env.CLAUDINHO_TEAM)
6879
7809
  );
@@ -6899,7 +7829,7 @@ function handlePipeError(stream) {
6899
7829
  }
6900
7830
  handlePipeError(process.stdout);
6901
7831
  handlePipeError(process.stderr);
6902
- var VERSION = "0.9.3";
7832
+ var VERSION = "0.9.4";
6903
7833
  var DISCLAIMER = "Claudinho is an independent fan project. Not affiliated with or endorsed by FIFA or Anthropic.";
6904
7834
  function ctxFrom(cmd) {
6905
7835
  let root = cmd;