@claudinho/mcp 0.10.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +292 -74
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -279,6 +279,8 @@ var EN = {
|
|
|
279
279
|
"bracket.slot.tbd": "TBD",
|
|
280
280
|
"live.data": "Live data: {source}",
|
|
281
281
|
"standings.unavailable": "Live standings unavailable.",
|
|
282
|
+
"standings.partial": "Partial table \u2014 {n} rows could not be read.",
|
|
283
|
+
"competition.unsupported": "Not available for this competition yet.",
|
|
282
284
|
"share.tryIt": "Try it: {line}",
|
|
283
285
|
"stage.group": "Group {group}",
|
|
284
286
|
"stage.groupStage": "Group stage",
|
|
@@ -309,6 +311,8 @@ var ES = {
|
|
|
309
311
|
"bracket.slot.tbd": "Por definir",
|
|
310
312
|
"live.data": "Datos en vivo: {source}",
|
|
311
313
|
"standings.unavailable": "Tabla en vivo no disponible.",
|
|
314
|
+
"standings.partial": "Tabla parcial \u2014 no se pudieron leer {n} filas.",
|
|
315
|
+
"competition.unsupported": "A\xFAn no disponible para esta competici\xF3n.",
|
|
312
316
|
"share.tryIt": "Pru\xE9balo: {line}",
|
|
313
317
|
"stage.group": "Grupo {group}",
|
|
314
318
|
"stage.groupStage": "Fase de grupos",
|
|
@@ -339,6 +343,8 @@ var PT = {
|
|
|
339
343
|
"bracket.slot.tbd": "A definir",
|
|
340
344
|
"live.data": "Dados ao vivo: {source}",
|
|
341
345
|
"standings.unavailable": "Classifica\xE7\xE3o ao vivo indispon\xEDvel.",
|
|
346
|
+
"standings.partial": "Tabela parcial \u2014 {n} linhas n\xE3o puderam ser lidas.",
|
|
347
|
+
"competition.unsupported": "Ainda n\xE3o dispon\xEDvel para esta competi\xE7\xE3o.",
|
|
342
348
|
"share.tryIt": "Experimente: {line}",
|
|
343
349
|
"stage.group": "Grupo {group}",
|
|
344
350
|
"stage.groupStage": "Fase de grupos",
|
|
@@ -369,6 +375,8 @@ var FR = {
|
|
|
369
375
|
"bracket.slot.tbd": "\xC0 d\xE9finir",
|
|
370
376
|
"live.data": "Donn\xE9es en direct : {source}",
|
|
371
377
|
"standings.unavailable": "Classement en direct indisponible.",
|
|
378
|
+
"standings.partial": "Classement partiel \u2014 {n} lignes n'ont pas pu \xEAtre lues.",
|
|
379
|
+
"competition.unsupported": "Pas encore disponible pour cette comp\xE9tition.",
|
|
372
380
|
"share.tryIt": "Essayez : {line}",
|
|
373
381
|
"stage.group": "Groupe {group}",
|
|
374
382
|
"stage.groupStage": "Phase de groupes",
|
|
@@ -2836,11 +2844,13 @@ function fixturesByGroup(group, fixtures = SCHEDULE) {
|
|
|
2836
2844
|
const g = group.toUpperCase();
|
|
2837
2845
|
return fixtures.filter((m) => (m.group ?? "").toUpperCase() === g).sort(byKickoff);
|
|
2838
2846
|
}
|
|
2847
|
+
function isUpcoming(m, now = /* @__PURE__ */ new Date()) {
|
|
2848
|
+
if (m.status === "CANCELLED" || m.status === "POSTPONED") return false;
|
|
2849
|
+
return Date.parse(m.kickoff) >= now.getTime();
|
|
2850
|
+
}
|
|
2839
2851
|
function nextFixtureForTeam(code, opts = {}) {
|
|
2840
2852
|
const from = opts.from ?? /* @__PURE__ */ new Date();
|
|
2841
|
-
return fixturesByTeam(code, opts.fixtures ?? SCHEDULE).find(
|
|
2842
|
-
(m) => new Date(m.kickoff).getTime() >= from.getTime()
|
|
2843
|
-
);
|
|
2853
|
+
return fixturesByTeam(code, opts.fixtures ?? SCHEDULE).find((m) => isUpcoming(m, from));
|
|
2844
2854
|
}
|
|
2845
2855
|
var LIVE_WINDOW_MS = 140 * 6e4;
|
|
2846
2856
|
var KNOCKOUT_EXTRA_TIME_MS = 60 * 6e4;
|
|
@@ -3186,7 +3196,7 @@ function toParticipant(raw) {
|
|
|
3186
3196
|
const providerId = opaqueId(raw.team?.id, ESPN_ID);
|
|
3187
3197
|
const known = productFlag(name) !== nationToFlag("");
|
|
3188
3198
|
return valid(
|
|
3189
|
-
providerId && known ? { kind: "team", providerId, team } : { kind: "slot", team }
|
|
3199
|
+
providerId && known ? { kind: "team", providerId, team } : { kind: "slot", ...providerId ? { providerId } : {}, team }
|
|
3190
3200
|
);
|
|
3191
3201
|
}
|
|
3192
3202
|
function mapStatus(st) {
|
|
@@ -3255,7 +3265,7 @@ function parseEspnEvent(raw, ctx = {}) {
|
|
|
3255
3265
|
if (awayP.kind !== "valid") return awayP;
|
|
3256
3266
|
const h = homeP.value;
|
|
3257
3267
|
const a = awayP.value;
|
|
3258
|
-
if (h.
|
|
3268
|
+
if (h.providerId !== void 0 && h.providerId === a.providerId) {
|
|
3259
3269
|
return definitiveNone("both competitors are the same team");
|
|
3260
3270
|
}
|
|
3261
3271
|
const home = homeP.value.team;
|
|
@@ -3408,8 +3418,12 @@ function parseEspnStandings(raw) {
|
|
|
3408
3418
|
const seenProviderIds = /* @__PURE__ */ new Set();
|
|
3409
3419
|
for (const child of children) {
|
|
3410
3420
|
const label = humanLabel(child?.name ?? child?.abbreviation);
|
|
3411
|
-
const letter = label.match(/Group\s+([A-L])/i)?.[1]?.toUpperCase();
|
|
3412
|
-
if (!letter)
|
|
3421
|
+
const letter = label.match(/Group\s+([A-L])(?![A-Za-z0-9])/i)?.[1]?.toUpperCase();
|
|
3422
|
+
if (!letter) {
|
|
3423
|
+
const rows = child?.standings?.entries;
|
|
3424
|
+
if (Array.isArray(rows) && rows.length > 0) complete = false;
|
|
3425
|
+
continue;
|
|
3426
|
+
}
|
|
3413
3427
|
if (seenGroups.has(letter)) {
|
|
3414
3428
|
complete = false;
|
|
3415
3429
|
continue;
|
|
@@ -3420,9 +3434,11 @@ function parseEspnStandings(raw) {
|
|
|
3420
3434
|
complete = false;
|
|
3421
3435
|
continue;
|
|
3422
3436
|
}
|
|
3437
|
+
let omitted = 0;
|
|
3423
3438
|
if (rawEntries.length > MAX_GROUP_ROWS) {
|
|
3424
3439
|
rowsTruncated = true;
|
|
3425
3440
|
complete = false;
|
|
3441
|
+
omitted += rawEntries.length - MAX_GROUP_ROWS;
|
|
3426
3442
|
}
|
|
3427
3443
|
const entries = takeBounded(rawEntries, MAX_GROUP_ROWS);
|
|
3428
3444
|
const seenCodes = /* @__PURE__ */ new Map();
|
|
@@ -3432,6 +3448,7 @@ function parseEspnStandings(raw) {
|
|
|
3432
3448
|
const r = entryToRow(e);
|
|
3433
3449
|
if (r.kind !== "valid") {
|
|
3434
3450
|
if (r.kind !== "definitive-none") complete = false;
|
|
3451
|
+
omitted += 1;
|
|
3435
3452
|
continue;
|
|
3436
3453
|
}
|
|
3437
3454
|
const { providerId, providerRank } = r.value;
|
|
@@ -3440,13 +3457,14 @@ function parseEspnStandings(raw) {
|
|
|
3440
3457
|
const codeCollision = priorHadId !== void 0 && (providerId === void 0 || priorHadId === false);
|
|
3441
3458
|
if (codeCollision || seenRanks.has(providerRank) || providerId !== void 0 && seenProviderIds.has(providerId)) {
|
|
3442
3459
|
complete = false;
|
|
3460
|
+
omitted += 1;
|
|
3443
3461
|
continue;
|
|
3444
3462
|
}
|
|
3445
3463
|
seenCodes.set(code, providerId !== void 0);
|
|
3446
3464
|
seenRanks.add(providerRank);
|
|
3447
3465
|
if (providerId !== void 0) seenProviderIds.add(providerId);
|
|
3448
3466
|
const { providerId: _dropId, providerRank: rank, ...row } = r.value;
|
|
3449
|
-
ranked.push({ row, rank });
|
|
3467
|
+
ranked.push({ row: { ...row, rank }, rank });
|
|
3450
3468
|
}
|
|
3451
3469
|
ranked.sort((a, b) => {
|
|
3452
3470
|
if (a.rank && b.rank && a.rank !== b.rank) return a.rank - b.rank;
|
|
@@ -3458,7 +3476,11 @@ function parseEspnStandings(raw) {
|
|
|
3458
3476
|
complete = false;
|
|
3459
3477
|
continue;
|
|
3460
3478
|
}
|
|
3461
|
-
out.push({
|
|
3479
|
+
out.push({
|
|
3480
|
+
group: letter,
|
|
3481
|
+
rows: ranked.map((x) => x.row),
|
|
3482
|
+
...omitted > 0 ? { partial: { omitted } } : {}
|
|
3483
|
+
});
|
|
3462
3484
|
}
|
|
3463
3485
|
return {
|
|
3464
3486
|
items: out,
|
|
@@ -3469,11 +3491,72 @@ function parseEspnStandings(raw) {
|
|
|
3469
3491
|
complete: complete && !rowsTruncated
|
|
3470
3492
|
};
|
|
3471
3493
|
}
|
|
3494
|
+
var ResponseTooLargeError = class extends Error {
|
|
3495
|
+
constructor(bytes, limit) {
|
|
3496
|
+
super(`response body exceeds ${limit} bytes (${bytes} seen)`);
|
|
3497
|
+
this.bytes = bytes;
|
|
3498
|
+
this.limit = limit;
|
|
3499
|
+
this.name = "ResponseTooLargeError";
|
|
3500
|
+
}
|
|
3501
|
+
bytes;
|
|
3502
|
+
limit;
|
|
3503
|
+
};
|
|
3504
|
+
function isStream(v) {
|
|
3505
|
+
return typeof v?.getReader === "function";
|
|
3506
|
+
}
|
|
3507
|
+
async function readJsonBounded(res, maxBytes) {
|
|
3508
|
+
const r = res;
|
|
3509
|
+
const declared = Number(r.headers?.get?.("content-length"));
|
|
3510
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
3511
|
+
if (isStream(r.body)) await r.body.cancel().catch(() => {
|
|
3512
|
+
});
|
|
3513
|
+
throw new ResponseTooLargeError(declared, maxBytes);
|
|
3514
|
+
}
|
|
3515
|
+
if (isStream(r.body)) {
|
|
3516
|
+
const reader = r.body.getReader();
|
|
3517
|
+
const chunks = [];
|
|
3518
|
+
let total = 0;
|
|
3519
|
+
for (; ; ) {
|
|
3520
|
+
const { done, value } = await reader.read();
|
|
3521
|
+
if (done) break;
|
|
3522
|
+
total += value.byteLength;
|
|
3523
|
+
if (total > maxBytes) {
|
|
3524
|
+
await reader.cancel().catch(() => {
|
|
3525
|
+
});
|
|
3526
|
+
throw new ResponseTooLargeError(total, maxBytes);
|
|
3527
|
+
}
|
|
3528
|
+
chunks.push(value);
|
|
3529
|
+
}
|
|
3530
|
+
return JSON.parse(new TextDecoder().decode(Buffer.concat(chunks)));
|
|
3531
|
+
}
|
|
3532
|
+
if (typeof r.text === "function") {
|
|
3533
|
+
const text = await r.text();
|
|
3534
|
+
const size2 = Buffer.byteLength(text);
|
|
3535
|
+
if (size2 > maxBytes) throw new ResponseTooLargeError(size2, maxBytes);
|
|
3536
|
+
return JSON.parse(text);
|
|
3537
|
+
}
|
|
3538
|
+
if (typeof r.json === "function") return r.json();
|
|
3539
|
+
throw new TypeError("response has no readable body");
|
|
3540
|
+
}
|
|
3472
3541
|
var ESPN_SOCCER = "https://site.api.espn.com/apis/site/v2/sports/soccer";
|
|
3473
3542
|
var DEFAULT_COMPETITION = "fifa.world";
|
|
3474
3543
|
var DEFAULT_BASE = `${ESPN_SOCCER}/${DEFAULT_COMPETITION}`;
|
|
3475
|
-
var USER_AGENT = `claudinho/${"0.10.
|
|
3544
|
+
var USER_AGENT = `claudinho/${"0.10.1"} (+https://github.com/arturogarrido/claudinho)`;
|
|
3476
3545
|
var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
3546
|
+
var DEFAULT_COOLDOWN_MS = 5 * 6e4;
|
|
3547
|
+
var MAX_COOLDOWN_MS = 15 * 6e4;
|
|
3548
|
+
function retryAfterMs(header, nowMs) {
|
|
3549
|
+
if (typeof header !== "string" || header.trim() === "") return DEFAULT_COOLDOWN_MS;
|
|
3550
|
+
const h = header.trim();
|
|
3551
|
+
let ms;
|
|
3552
|
+
if (/^\d+$/.test(h)) ms = Number(h) * 1e3;
|
|
3553
|
+
else {
|
|
3554
|
+
const at = Date.parse(h);
|
|
3555
|
+
if (Number.isFinite(at)) ms = at - nowMs;
|
|
3556
|
+
}
|
|
3557
|
+
if (ms === void 0 || !Number.isFinite(ms)) return DEFAULT_COOLDOWN_MS;
|
|
3558
|
+
return Math.min(Math.max(ms, 0), MAX_COOLDOWN_MS);
|
|
3559
|
+
}
|
|
3477
3560
|
function competitionBase(slug) {
|
|
3478
3561
|
return `${ESPN_SOCCER}/${slug}`;
|
|
3479
3562
|
}
|
|
@@ -3482,6 +3565,8 @@ var STANDINGS_SHARE_MS = 3e4;
|
|
|
3482
3565
|
var ProviderError = class extends Error {
|
|
3483
3566
|
kind;
|
|
3484
3567
|
status;
|
|
3568
|
+
/** For a throttle: how long the adapter will refuse to fetch (bounded). */
|
|
3569
|
+
retryAfterMs;
|
|
3485
3570
|
constructor(message, kind, status) {
|
|
3486
3571
|
super(message);
|
|
3487
3572
|
this.name = "ProviderError";
|
|
@@ -3505,6 +3590,7 @@ function usableProviderItems(kind, parsed, hasUsableRecord = parsed.items.length
|
|
|
3505
3590
|
var EspnAdapter = class {
|
|
3506
3591
|
constructor(opts = {}) {
|
|
3507
3592
|
this.opts = opts;
|
|
3593
|
+
this.clock = opts.now ?? (() => Date.now());
|
|
3508
3594
|
const expected = opts.expectedStandingsGroups ?? (opts.baseUrl === void 0 ? groups() : void 0);
|
|
3509
3595
|
this.expectedStandingsGroups = expected ? [...expected] : void 0;
|
|
3510
3596
|
this.standingsFallbackGroups = opts.baseUrl === void 0 && expected ? [...expected] : void 0;
|
|
@@ -3530,6 +3616,53 @@ var EspnAdapter = class {
|
|
|
3530
3616
|
* throttle (persist a backoff) from an ordinary blip.
|
|
3531
3617
|
*/
|
|
3532
3618
|
lastError;
|
|
3619
|
+
/**
|
|
3620
|
+
* A retained throttle (audit A12): after a 429/403 every call inside the
|
|
3621
|
+
* window throws the provider's last answer WITHOUT a request. A server-
|
|
3622
|
+
* lifetime MCP adapter is covered by this alone; the CLI pre-arms each
|
|
3623
|
+
* process from its persisted cache via `armCooldown`.
|
|
3624
|
+
*/
|
|
3625
|
+
cooldownUntilMs;
|
|
3626
|
+
cooldownError;
|
|
3627
|
+
cooldownListeners = /* @__PURE__ */ new Set();
|
|
3628
|
+
clock;
|
|
3629
|
+
/** Epoch ms until which requests are refused, when a cooldown is armed. */
|
|
3630
|
+
get cooldownUntil() {
|
|
3631
|
+
return this.cooldownUntilMs;
|
|
3632
|
+
}
|
|
3633
|
+
/**
|
|
3634
|
+
* Arm the cooldown from outside — a fresh CLI process reading the backoff
|
|
3635
|
+
* its refresher persisted. The retained error reads as a throttle so every
|
|
3636
|
+
* caller's `degraded` path and the refresher's persistence treat it as one.
|
|
3637
|
+
*/
|
|
3638
|
+
armCooldown(untilMs, reason) {
|
|
3639
|
+
const nowMs = this.clock();
|
|
3640
|
+
const error = reason ?? new ProviderError("ESPN request skipped: provider cooldown in effect", "http", 429);
|
|
3641
|
+
error.retryAfterMs = Math.max(0, untilMs - nowMs);
|
|
3642
|
+
this.arm(untilMs, error);
|
|
3643
|
+
}
|
|
3644
|
+
/**
|
|
3645
|
+
* Be told whenever the cooldown window is armed or EXTENDED — the way a
|
|
3646
|
+
* caller persists a throttle that arrives from a still-running request after
|
|
3647
|
+
* its own call already returned (review P2 on #128). Returns unsubscribe.
|
|
3648
|
+
*/
|
|
3649
|
+
onCooldown(listener) {
|
|
3650
|
+
this.cooldownListeners.add(listener);
|
|
3651
|
+
return () => {
|
|
3652
|
+
this.cooldownListeners.delete(listener);
|
|
3653
|
+
};
|
|
3654
|
+
}
|
|
3655
|
+
/**
|
|
3656
|
+
* The ONE place a window is set. Concurrent requests can each carry a
|
|
3657
|
+
* Retry-After; the LATEST expiry wins — a shorter one arriving second must
|
|
3658
|
+
* never shorten a longer active window (review P2 on #128).
|
|
3659
|
+
*/
|
|
3660
|
+
arm(untilMs, error) {
|
|
3661
|
+
if (this.cooldownUntilMs !== void 0 && untilMs <= this.cooldownUntilMs) return;
|
|
3662
|
+
this.cooldownUntilMs = untilMs;
|
|
3663
|
+
this.cooldownError = error;
|
|
3664
|
+
for (const listener of this.cooldownListeners) listener(untilMs);
|
|
3665
|
+
}
|
|
3533
3666
|
async fetchByDate(dateISO) {
|
|
3534
3667
|
return this.fetchScoreboard(toEspnDate(dateISO));
|
|
3535
3668
|
}
|
|
@@ -3617,6 +3750,11 @@ var EspnAdapter = class {
|
|
|
3617
3750
|
return usableProviderItems("scoreboard", parsed);
|
|
3618
3751
|
}
|
|
3619
3752
|
async get(url) {
|
|
3753
|
+
const nowMs = this.clock();
|
|
3754
|
+
if (this.cooldownError && this.cooldownUntilMs !== void 0 && nowMs < this.cooldownUntilMs) {
|
|
3755
|
+
this.lastError = this.cooldownError;
|
|
3756
|
+
throw this.cooldownError;
|
|
3757
|
+
}
|
|
3620
3758
|
const doFetch = this.opts.fetchImpl ?? fetch;
|
|
3621
3759
|
const controller = new AbortController();
|
|
3622
3760
|
const timer = setTimeout(
|
|
@@ -3638,19 +3776,24 @@ var EspnAdapter = class {
|
|
|
3638
3776
|
throw e?.name === "AbortError" ? new ProviderError(`ESPN request timed out: ${url}`, "timeout") : new ProviderError(`ESPN request failed: ${e?.message ?? e}`, "http");
|
|
3639
3777
|
}
|
|
3640
3778
|
if (!res.ok) {
|
|
3641
|
-
|
|
3779
|
+
const pe = new ProviderError(
|
|
3642
3780
|
`ESPN request failed: ${res.status} ${res.statusText}`,
|
|
3643
3781
|
"http",
|
|
3644
3782
|
res.status
|
|
3645
3783
|
);
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3784
|
+
if (pe.throttled) {
|
|
3785
|
+
const receiptMs = this.clock();
|
|
3786
|
+
pe.retryAfterMs = retryAfterMs(res.headers?.get?.("retry-after"), receiptMs);
|
|
3787
|
+
this.arm(receiptMs + pe.retryAfterMs, pe);
|
|
3788
|
+
}
|
|
3789
|
+
throw pe;
|
|
3650
3790
|
}
|
|
3651
3791
|
try {
|
|
3652
|
-
return await res
|
|
3792
|
+
return await readJsonBounded(res, MAX_RESPONSE_BYTES);
|
|
3653
3793
|
} catch (e) {
|
|
3794
|
+
if (e instanceof ResponseTooLargeError) {
|
|
3795
|
+
throw new ProviderError(`ESPN response too large: ${e.bytes} bytes`, "parse");
|
|
3796
|
+
}
|
|
3654
3797
|
throw new ProviderError(
|
|
3655
3798
|
`ESPN response unparseable: ${e?.message ?? e}`,
|
|
3656
3799
|
"parse"
|
|
@@ -3693,14 +3836,30 @@ function hasGroupStarted(group, tables) {
|
|
|
3693
3836
|
function matchesPerTeamInGroup(teamCount) {
|
|
3694
3837
|
return Math.max(0, teamCount - 1);
|
|
3695
3838
|
}
|
|
3696
|
-
function isGroupStandingsComplete(table) {
|
|
3697
|
-
|
|
3839
|
+
function isGroupStandingsComplete(table, expectedTeams) {
|
|
3840
|
+
if (!table || table.partial) return false;
|
|
3841
|
+
const n = table.rows.length;
|
|
3698
3842
|
if (n < 2) return false;
|
|
3699
|
-
|
|
3843
|
+
if (expectedTeams !== void 0 && n < expectedTeams) return false;
|
|
3844
|
+
const required = matchesPerTeamInGroup(Math.max(n, expectedTeams ?? n));
|
|
3700
3845
|
return table.rows.every((r) => r.played >= required);
|
|
3701
3846
|
}
|
|
3847
|
+
function bundledGroupSize(group) {
|
|
3848
|
+
const codes = /* @__PURE__ */ new Set();
|
|
3849
|
+
for (const m of fixturesByGroup(group)) {
|
|
3850
|
+
codes.add(m.home.code);
|
|
3851
|
+
codes.add(m.away.code);
|
|
3852
|
+
}
|
|
3853
|
+
return codes.size > 0 ? codes.size : void 0;
|
|
3854
|
+
}
|
|
3702
3855
|
function isGroupComplete(group, tables) {
|
|
3703
|
-
return isGroupStandingsComplete(
|
|
3856
|
+
return isGroupStandingsComplete(
|
|
3857
|
+
tables.find((t2) => t2.group === group),
|
|
3858
|
+
bundledGroupSize(group)
|
|
3859
|
+
);
|
|
3860
|
+
}
|
|
3861
|
+
function isGroupPartial(group, tables) {
|
|
3862
|
+
return tables.find((t2) => t2.group === group)?.partial !== void 0;
|
|
3704
3863
|
}
|
|
3705
3864
|
function resolveWinner(match) {
|
|
3706
3865
|
if (!isFinished(match.status)) return void 0;
|
|
@@ -3755,7 +3914,7 @@ function resolveSlot(ref, ctx, liveTeam, fixtureInMergedSet = false) {
|
|
|
3755
3914
|
return tbd(ref.label);
|
|
3756
3915
|
case "group": {
|
|
3757
3916
|
if (liveParticipant) return liveParticipant;
|
|
3758
|
-
if (!ctx.standingsDegraded && hasGroupStarted(ref.group, ctx.tables)) {
|
|
3917
|
+
if (!ctx.standingsDegraded && !isGroupPartial(ref.group, ctx.tables) && hasGroupStarted(ref.group, ctx.tables)) {
|
|
3759
3918
|
const team = teamFromStandings(ref.group, ref.position, ctx.tables);
|
|
3760
3919
|
if (team) {
|
|
3761
3920
|
const status = isGroupComplete(ref.group, ctx.tables) ? "confirmed" : "projected";
|
|
@@ -4389,13 +4548,17 @@ function resolveCompetition(explicit) {
|
|
|
4389
4548
|
}
|
|
4390
4549
|
return DEFAULT_COMPETITION;
|
|
4391
4550
|
}
|
|
4551
|
+
var BUNDLE_COMPETITION = DEFAULT_COMPETITION;
|
|
4552
|
+
function bundleApplies(competition = resolveCompetition()) {
|
|
4553
|
+
return competition === BUNDLE_COMPETITION;
|
|
4554
|
+
}
|
|
4392
4555
|
var KNOWN_SOURCES = ["espn"];
|
|
4393
4556
|
function makeAdapter(source = "espn", opts = {}) {
|
|
4394
4557
|
switch (source) {
|
|
4395
4558
|
case "espn": {
|
|
4396
4559
|
const competition = resolveCompetition();
|
|
4397
4560
|
const baseUrl = competition === DEFAULT_COMPETITION ? void 0 : competitionBase(competition);
|
|
4398
|
-
return new EspnAdapter({ baseUrl, enrichGroups: opts.enrichGroups });
|
|
4561
|
+
return new EspnAdapter({ baseUrl, enrichGroups: opts.enrichGroups, now: opts.now });
|
|
4399
4562
|
}
|
|
4400
4563
|
default:
|
|
4401
4564
|
throw new Error(
|
|
@@ -4413,7 +4576,7 @@ function liveSourceLabel(source) {
|
|
|
4413
4576
|
return known[source] ?? source.charAt(0).toUpperCase() + source.slice(1);
|
|
4414
4577
|
}
|
|
4415
4578
|
async function getMatchesForDate(adapter, dateISO) {
|
|
4416
|
-
const base = allFixtures();
|
|
4579
|
+
const base = bundleApplies() ? allFixtures() : [];
|
|
4417
4580
|
const day = dateISO.slice(0, 10);
|
|
4418
4581
|
try {
|
|
4419
4582
|
const live = adapter.fetchWindow ? await adapter.fetchWindow(shiftUtcDate(day, -1), shiftUtcDate(day, 1)) : await adapter.fetchByDate(day);
|
|
@@ -4458,21 +4621,27 @@ function knockoutWindow() {
|
|
|
4458
4621
|
return knockoutWindowMemo;
|
|
4459
4622
|
}
|
|
4460
4623
|
async function getBracket(adapter, opts = {}) {
|
|
4624
|
+
if (!bundleApplies()) {
|
|
4625
|
+
const view2 = { stages: [], degraded: false, standingsDegraded: false, unsupported: true };
|
|
4626
|
+
return { view: view2, degraded: false, standingsDegraded: false, unsupported: true };
|
|
4627
|
+
}
|
|
4461
4628
|
const topology = loadBracketTopology();
|
|
4462
4629
|
const base = allFixtures().filter((m) => m.stage !== "GROUP" && m.stage !== "FRIENDLY");
|
|
4463
4630
|
let matches = base;
|
|
4464
4631
|
let liveDegraded = true;
|
|
4465
4632
|
let source;
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4633
|
+
const win = knockoutWindow();
|
|
4634
|
+
if (adapter.fetchWindow && win) {
|
|
4635
|
+
try {
|
|
4636
|
+
const live = await adapter.fetchWindow(win.start, win.end);
|
|
4637
|
+
matches = mergeLive(base, live);
|
|
4638
|
+
liveDegraded = false;
|
|
4639
|
+
source = adapter.name;
|
|
4640
|
+
} catch {
|
|
4641
|
+
}
|
|
4473
4642
|
}
|
|
4474
4643
|
const standings = await getStandings(adapter);
|
|
4475
|
-
if (!source && !standings.degraded && standings.source) {
|
|
4644
|
+
if (!source && !standings.degraded && standings.source && standings.tables.length > 0) {
|
|
4476
4645
|
source = standings.source;
|
|
4477
4646
|
}
|
|
4478
4647
|
const view = buildBracketView(
|
|
@@ -4494,18 +4663,18 @@ async function getBracket(adapter, opts = {}) {
|
|
|
4494
4663
|
}
|
|
4495
4664
|
var EXTRA_TIME_SLACK_MS = 60 * 6e4;
|
|
4496
4665
|
async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Date()) {
|
|
4666
|
+
if (!bundleApplies()) return { match: void 0, degraded: false, unsupported: true };
|
|
4497
4667
|
const nowMs = now.getTime();
|
|
4498
4668
|
let fixtures = allFixtures();
|
|
4499
4669
|
let overlayFailed = false;
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
fixtures = mergeLive(
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
);
|
|
4670
|
+
const win = knockoutWindow();
|
|
4671
|
+
if (adapter.fetchWindow && win) {
|
|
4672
|
+
try {
|
|
4673
|
+
fixtures = mergeLive(fixtures, await adapter.fetchWindow(win.start, win.end));
|
|
4674
|
+
} catch {
|
|
4675
|
+
overlayFailed = true;
|
|
4507
4676
|
}
|
|
4508
|
-
}
|
|
4677
|
+
} else {
|
|
4509
4678
|
overlayFailed = true;
|
|
4510
4679
|
}
|
|
4511
4680
|
const candidate = fixturesByTeam(code, fixtures).find((m) => {
|
|
@@ -4521,23 +4690,27 @@ async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Dat
|
|
|
4521
4690
|
return { match: next, degraded: overlayFailed };
|
|
4522
4691
|
}
|
|
4523
4692
|
async function getNextFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Date()) {
|
|
4693
|
+
if (!bundleApplies()) return { fixture: void 0, degraded: false, unsupported: true };
|
|
4524
4694
|
const base = allFixtures();
|
|
4525
4695
|
let matches = base;
|
|
4526
4696
|
let degraded = true;
|
|
4527
4697
|
let liveById;
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4698
|
+
const win = knockoutWindow();
|
|
4699
|
+
if (adapter.fetchWindow && win) {
|
|
4700
|
+
try {
|
|
4701
|
+
const live = await adapter.fetchWindow(win.start, win.end);
|
|
4702
|
+
matches = mergeLive(base, live);
|
|
4703
|
+
degraded = false;
|
|
4704
|
+
liveById = new Set(live.map((m) => m.id));
|
|
4705
|
+
} catch {
|
|
4706
|
+
}
|
|
4535
4707
|
}
|
|
4536
4708
|
const fixture = nextFixtureForTeam(code, { from: now, fixtures: matches });
|
|
4537
4709
|
const source = fixture && liveById?.has(fixture.id) ? adapter.name : void 0;
|
|
4538
4710
|
return { fixture, degraded, source };
|
|
4539
4711
|
}
|
|
4540
4712
|
async function getMatchById(adapter, id) {
|
|
4713
|
+
if (!bundleApplies()) return { match: void 0, degraded: false, unsupported: true };
|
|
4541
4714
|
const base = allFixtures().find((m) => m.id === id);
|
|
4542
4715
|
if (!base) return { match: void 0, degraded: false };
|
|
4543
4716
|
const day = base.kickoff.slice(0, 10);
|
|
@@ -4975,11 +5148,15 @@ var PolymarketProvider = class {
|
|
|
4975
5148
|
if (!res.ok) {
|
|
4976
5149
|
throw new Error(`Polymarket request failed: ${res.status} ${res.statusText}`);
|
|
4977
5150
|
}
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
5151
|
+
let data;
|
|
5152
|
+
try {
|
|
5153
|
+
data = await readJsonBounded(res, MAX_RESPONSE_BYTES);
|
|
5154
|
+
} catch (e) {
|
|
5155
|
+
if (e instanceof ResponseTooLargeError) {
|
|
5156
|
+
throw new Error(`Polymarket response too large: ${e.bytes} bytes`);
|
|
5157
|
+
}
|
|
5158
|
+
throw e;
|
|
4981
5159
|
}
|
|
4982
|
-
const data = await res.json();
|
|
4983
5160
|
if (Array.isArray(data) && data.length > 1) {
|
|
4984
5161
|
return ambiguous("slug returned more than one event");
|
|
4985
5162
|
}
|
|
@@ -5386,10 +5563,13 @@ function formatShareTable(input, options = {}) {
|
|
|
5386
5563
|
input.emptyNote ?? (input.degraded ? "Live standings unavailable." : "No standings available.")
|
|
5387
5564
|
);
|
|
5388
5565
|
} else {
|
|
5389
|
-
for (const { group, rows } of input.tables) {
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5566
|
+
for (const { group, rows, partial } of input.tables) {
|
|
5567
|
+
const lines = [`Group ${group} \xB7 standings`, "", ...rows.map((r, i) => tableRow(r, r.rank ?? i + 1))];
|
|
5568
|
+
if (partial) {
|
|
5569
|
+
const n = partial.omitted;
|
|
5570
|
+
lines.push("", `(partial table \u2014 ${n} row${n === 1 ? "" : "s"} unreadable; positions are the provider's ranks)`);
|
|
5571
|
+
}
|
|
5572
|
+
blocks.push(lines.join("\n"));
|
|
5393
5573
|
}
|
|
5394
5574
|
if (input.degraded) {
|
|
5395
5575
|
blocks.push("(Live standings unavailable \u2014 group roster, not live results.)");
|
|
@@ -5754,9 +5934,19 @@ ${matchList(matches, "No matches in play right now.", opts)}`;
|
|
|
5754
5934
|
};
|
|
5755
5935
|
}
|
|
5756
5936
|
async function toolGetMatch(args) {
|
|
5757
|
-
const { match, degraded, source: liveSource } = await getMatchById(
|
|
5937
|
+
const { match, degraded, source: liveSource, unsupported } = await getMatchById(
|
|
5938
|
+
resolveAdapter(args),
|
|
5939
|
+
args.id
|
|
5940
|
+
);
|
|
5758
5941
|
if (!match) {
|
|
5759
|
-
return {
|
|
5942
|
+
return {
|
|
5943
|
+
text: withDisclaimer(
|
|
5944
|
+
unsupported ? t(args.lang, "competition.unsupported") : `No match found with id ${args.id}.`,
|
|
5945
|
+
void 0,
|
|
5946
|
+
args.lang
|
|
5947
|
+
),
|
|
5948
|
+
data: { match: null }
|
|
5949
|
+
};
|
|
5760
5950
|
}
|
|
5761
5951
|
const opts = fmtOpts(args);
|
|
5762
5952
|
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
@@ -5789,7 +5979,11 @@ ${marketBlock(marketSignal, match).join("\n")}` : base;
|
|
|
5789
5979
|
async function toolGetStandings(args) {
|
|
5790
5980
|
const { tables, degraded, source } = await getStandings(resolveAdapter(args), args.group);
|
|
5791
5981
|
const boundedTables = boundedRecords(tables);
|
|
5792
|
-
const shaped = boundedTables.items.map((tb) => ({
|
|
5982
|
+
const shaped = boundedTables.items.map((tb) => ({
|
|
5983
|
+
group: tb.group,
|
|
5984
|
+
standings: tb.rows,
|
|
5985
|
+
...tb.partial ? { partial: tb.partial } : {}
|
|
5986
|
+
}));
|
|
5793
5987
|
if (shaped.length === 0) {
|
|
5794
5988
|
const g = args.group?.toUpperCase();
|
|
5795
5989
|
const msg = degraded ? t(args.lang, "standings.unavailable") : g ? `No group "${g}".` : "No standings available.";
|
|
@@ -5798,7 +5992,11 @@ async function toolGetStandings(args) {
|
|
|
5798
5992
|
data: { degraded, source: source ?? null, tables: args.group ? null : [] }
|
|
5799
5993
|
};
|
|
5800
5994
|
}
|
|
5801
|
-
let text = shaped.map((
|
|
5995
|
+
let text = shaped.map((tb) => {
|
|
5996
|
+
const block = standingsTable(tb.group, tb.standings);
|
|
5997
|
+
return tb.partial ? `${block}
|
|
5998
|
+
(${t(args.lang, "standings.partial", { n: String(tb.partial.omitted) })})` : block;
|
|
5999
|
+
}).join("\n\n");
|
|
5802
6000
|
text += truncationNote(boundedTables);
|
|
5803
6001
|
if (degraded) text += "\n\n(Live standings unavailable \u2014 showing the group roster.)";
|
|
5804
6002
|
return {
|
|
@@ -5819,10 +6017,16 @@ async function toolGetBracket(args) {
|
|
|
5819
6017
|
data: { view: null }
|
|
5820
6018
|
};
|
|
5821
6019
|
}
|
|
5822
|
-
const { view, degraded, standingsDegraded, source } = await getBracket(
|
|
6020
|
+
const { view, degraded, standingsDegraded, source, unsupported } = await getBracket(
|
|
5823
6021
|
resolveAdapter(args),
|
|
5824
6022
|
filter ? { stage: filter, lang: args.lang } : { lang: args.lang }
|
|
5825
6023
|
);
|
|
6024
|
+
if (unsupported) {
|
|
6025
|
+
return {
|
|
6026
|
+
text: withDisclaimer(t(args.lang, "competition.unsupported"), void 0, args.lang),
|
|
6027
|
+
data: { degraded, standingsDegraded, source: null, view }
|
|
6028
|
+
};
|
|
6029
|
+
}
|
|
5826
6030
|
let text = formatBracketList(view, { footer: false, locale: args.lang, tz: args.tz });
|
|
5827
6031
|
if (degraded) {
|
|
5828
6032
|
text += `
|
|
@@ -5843,18 +6047,20 @@ async function standingsResourceText(group, adapter) {
|
|
|
5843
6047
|
const { tables, degraded, source } = await getStandings(adapter, g);
|
|
5844
6048
|
const tb = tables[0];
|
|
5845
6049
|
let text = tb ? standingsTable(tb.group, tb.rows) : degraded ? "Live standings unavailable." : `No group ${g}.`;
|
|
6050
|
+
if (tb?.partial) text += `
|
|
6051
|
+
(${t(void 0, "standings.partial", { n: String(tb.partial.omitted) })})`;
|
|
5846
6052
|
if (degraded && tb) text += "\n\n(Live standings unavailable \u2014 showing the group roster.)";
|
|
5847
6053
|
return withDisclaimer(text, source);
|
|
5848
6054
|
}
|
|
5849
6055
|
async function toolGetNextFixture(args) {
|
|
5850
6056
|
const code = args.team.toUpperCase();
|
|
5851
|
-
const { fixture, degraded, source } = await getNextFixtureForTeam(
|
|
6057
|
+
const { fixture, degraded, source, unsupported } = await getNextFixtureForTeam(
|
|
5852
6058
|
resolveAdapter(args),
|
|
5853
6059
|
code,
|
|
5854
6060
|
args.now ?? /* @__PURE__ */ new Date()
|
|
5855
6061
|
);
|
|
5856
6062
|
if (!fixture) {
|
|
5857
|
-
const msg = degraded ? `Couldn't reach the data provider \u2014 no upcoming fixture confirmed for ${code}.` : `No upcoming fixture found for ${code}.`;
|
|
6063
|
+
const msg = unsupported ? t(args.lang, "competition.unsupported") : degraded ? `Couldn't reach the data provider \u2014 no upcoming fixture confirmed for ${code}.` : `No upcoming fixture found for ${code}.`;
|
|
5858
6064
|
return {
|
|
5859
6065
|
text: withDisclaimer(msg, void 0, args.lang),
|
|
5860
6066
|
data: { team: code, fixture: null, degraded, source: source ?? null }
|
|
@@ -5886,12 +6092,12 @@ async function toolGetMarketSignal(args) {
|
|
|
5886
6092
|
const provider = resolveMarketProvider(args);
|
|
5887
6093
|
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
5888
6094
|
if (args.matchId) {
|
|
5889
|
-
const { match } = await getMatchById(resolveAdapter(args), args.matchId);
|
|
6095
|
+
const { match, unsupported } = await getMatchById(resolveAdapter(args), args.matchId);
|
|
5890
6096
|
const relevant = match ? marketRelevant(match, now) : false;
|
|
5891
6097
|
const batch2 = match && relevant ? await getMarketSignals(provider, [match], MARKETS_TOOL_OPTS) : { results: /* @__PURE__ */ new Map(), complete: true };
|
|
5892
6098
|
const sig = match ? resolvedValues(batch2).get(match.id) : void 0;
|
|
5893
6099
|
const shown2 = batch2.complete && match && sig && marketDisplayable(match, sig) ? sig : void 0;
|
|
5894
|
-
const text2 = !match ? `No match found with id ${args.matchId}.` : !batch2.complete ? `Market data unavailable or incomplete for ${marketHeader(match, args)} \u2014 this match could not be checked.` : shown2 ? marketText(match, shown2, args) : noSignalText(match, args, now);
|
|
6100
|
+
const text2 = !match ? unsupported ? t(args.lang, "competition.unsupported") : `No match found with id ${args.matchId}.` : !batch2.complete ? `Market data unavailable or incomplete for ${marketHeader(match, args)} \u2014 this match could not be checked.` : shown2 ? marketText(match, shown2, args) : noSignalText(match, args, now);
|
|
5895
6101
|
return {
|
|
5896
6102
|
text: withDisclaimer(text2),
|
|
5897
6103
|
data: {
|
|
@@ -5904,12 +6110,16 @@ async function toolGetMarketSignal(args) {
|
|
|
5904
6110
|
}
|
|
5905
6111
|
if (args.team) {
|
|
5906
6112
|
const code = args.team.toUpperCase();
|
|
5907
|
-
const { match: fixture, degraded } = await marketFixtureForTeam(
|
|
6113
|
+
const { match: fixture, degraded, unsupported } = await marketFixtureForTeam(
|
|
6114
|
+
resolveAdapter(args),
|
|
6115
|
+
code,
|
|
6116
|
+
now
|
|
6117
|
+
);
|
|
5908
6118
|
const relevant = fixture ? marketRelevant(fixture, now) : false;
|
|
5909
6119
|
const batch2 = fixture && relevant ? await getMarketSignals(provider, [fixture], MARKETS_TOOL_OPTS) : { results: /* @__PURE__ */ new Map(), complete: true };
|
|
5910
6120
|
const sig = fixture ? resolvedValues(batch2).get(fixture.id) : void 0;
|
|
5911
6121
|
const shown2 = batch2.complete && fixture && sig && marketDisplayable(fixture, sig) ? sig : void 0;
|
|
5912
|
-
const text2 = !fixture ? degraded ? `Live feed unavailable \u2014 can't resolve ${code}'s next fixture right now.` : `No upcoming fixture found for ${code}.` : !batch2.complete ? `Market data unavailable or incomplete for ${marketHeader(fixture, args)} \u2014 this match could not be checked.` : shown2 ? marketText(fixture, shown2, args) : noSignalText(fixture, args, now);
|
|
6122
|
+
const text2 = !fixture ? unsupported ? t(args.lang, "competition.unsupported") : degraded ? `Live feed unavailable \u2014 can't resolve ${code}'s next fixture right now.` : `No upcoming fixture found for ${code}.` : !batch2.complete ? `Market data unavailable or incomplete for ${marketHeader(fixture, args)} \u2014 this match could not be checked.` : shown2 ? marketText(fixture, shown2, args) : noSignalText(fixture, args, now);
|
|
5913
6123
|
return {
|
|
5914
6124
|
text: withDisclaimer(text2),
|
|
5915
6125
|
data: {
|
|
@@ -6069,7 +6279,12 @@ async function toolGetShareSnippet(args) {
|
|
|
6069
6279
|
degraded: degraded2,
|
|
6070
6280
|
informationalOnly: true,
|
|
6071
6281
|
snippet,
|
|
6072
|
-
|
|
6282
|
+
// The structured card keeps the verdict the snippet warns about (A01).
|
|
6283
|
+
tables: boundedRecords(tables).items.map((tb) => ({
|
|
6284
|
+
group: tb.group,
|
|
6285
|
+
standings: tb.rows,
|
|
6286
|
+
...tb.partial ? { partial: tb.partial } : {}
|
|
6287
|
+
}))
|
|
6073
6288
|
}
|
|
6074
6289
|
};
|
|
6075
6290
|
}
|
|
@@ -6085,7 +6300,7 @@ async function toolGetShareSnippet(args) {
|
|
|
6085
6300
|
data: { kind: "bracket", view: null }
|
|
6086
6301
|
};
|
|
6087
6302
|
}
|
|
6088
|
-
const { view, degraded: degraded2, source: source2 } = await getBracket(
|
|
6303
|
+
const { view, degraded: degraded2, source: source2, unsupported } = await getBracket(
|
|
6089
6304
|
resolveAdapter(args),
|
|
6090
6305
|
stageFilter ? { stage: stageFilter, lang: args.lang } : { lang: args.lang }
|
|
6091
6306
|
);
|
|
@@ -6094,7 +6309,7 @@ async function toolGetShareSnippet(args) {
|
|
|
6094
6309
|
view,
|
|
6095
6310
|
source: degraded2 ? void 0 : source2,
|
|
6096
6311
|
installLine: stageFilter ? `npx @claudinho/cli bracket ${stageFilter}` : "npx @claudinho/cli bracket",
|
|
6097
|
-
emptyNote: t(args.lang, "bracket.empty")
|
|
6312
|
+
emptyNote: unsupported ? t(args.lang, "competition.unsupported") : t(args.lang, "bracket.empty")
|
|
6098
6313
|
},
|
|
6099
6314
|
{ ...options, locale: args.lang, tz: args.tz }
|
|
6100
6315
|
);
|
|
@@ -6113,7 +6328,10 @@ async function toolGetShareSnippet(args) {
|
|
|
6113
6328
|
};
|
|
6114
6329
|
}
|
|
6115
6330
|
if (args.matchId) {
|
|
6116
|
-
const { match, degraded: degraded2, source: source2 } = await getMatchById(
|
|
6331
|
+
const { match, degraded: degraded2, source: source2, unsupported } = await getMatchById(
|
|
6332
|
+
resolveAdapter(args),
|
|
6333
|
+
args.matchId
|
|
6334
|
+
);
|
|
6117
6335
|
const matches = match ? [match] : [];
|
|
6118
6336
|
const market2 = await signalsFor(matches);
|
|
6119
6337
|
return shareResult(
|
|
@@ -6127,7 +6345,7 @@ async function toolGetShareSnippet(args) {
|
|
|
6127
6345
|
marketComplete: market2.complete,
|
|
6128
6346
|
source: source2,
|
|
6129
6347
|
degraded: degraded2,
|
|
6130
|
-
emptyNote: `No match found with id ${args.matchId}.`,
|
|
6348
|
+
emptyNote: unsupported ? t(args.lang, "competition.unsupported") : `No match found with id ${args.matchId}.`,
|
|
6131
6349
|
installLine: `npx @claudinho/cli match ${args.matchId}`,
|
|
6132
6350
|
tz: args.tz,
|
|
6133
6351
|
locale: args.lang
|
|
@@ -6137,7 +6355,7 @@ async function toolGetShareSnippet(args) {
|
|
|
6137
6355
|
}
|
|
6138
6356
|
if (args.team) {
|
|
6139
6357
|
const code = args.team.toUpperCase();
|
|
6140
|
-
const { fixture, degraded: degraded2, source: source2 } = await getNextFixtureForTeam(
|
|
6358
|
+
const { fixture, degraded: degraded2, source: source2, unsupported } = await getNextFixtureForTeam(
|
|
6141
6359
|
resolveAdapter(args),
|
|
6142
6360
|
code,
|
|
6143
6361
|
args.now ?? /* @__PURE__ */ new Date()
|
|
@@ -6158,7 +6376,7 @@ async function toolGetShareSnippet(args) {
|
|
|
6158
6376
|
// with get_next_fixture (a static group fixture carries no source).
|
|
6159
6377
|
source: source2,
|
|
6160
6378
|
degraded: degraded2,
|
|
6161
|
-
emptyNote: degraded2 ? `Couldn't reach the data provider \u2014 no upcoming fixture confirmed for ${code}.` : `No upcoming fixture found for ${code}.`,
|
|
6379
|
+
emptyNote: unsupported ? t(args.lang, "competition.unsupported") : degraded2 ? `Couldn't reach the data provider \u2014 no upcoming fixture confirmed for ${code}.` : `No upcoming fixture found for ${code}.`,
|
|
6162
6380
|
installLine: `npx @claudinho/cli next ${code}`,
|
|
6163
6381
|
tz: args.tz,
|
|
6164
6382
|
locale: args.lang
|
|
@@ -6197,7 +6415,7 @@ async function toolGetShareSnippet(args) {
|
|
|
6197
6415
|
|
|
6198
6416
|
// src/server.ts
|
|
6199
6417
|
var SERVER_NAME = "claudinho";
|
|
6200
|
-
var SERVER_VERSION = "0.10.
|
|
6418
|
+
var SERVER_VERSION = "0.10.1";
|
|
6201
6419
|
var VOICE = asFlavorLevel(process.env.CLAUDINHO_FLAVOR) === "off" ? "" : `
|
|
6202
6420
|
Voice: when relaying scores, narrate with lively, regionally-appropriate football-commentary energy in the user's language. Each match line may end with a short exclamation ("\u2014 \xA1GOOOOL!") \u2014 use it as a tone cue. Keep every fact exact; never invent details and never impersonate or name a real commentator.`;
|
|
6203
6421
|
var INSTRUCTIONS = `Claudinho serves live scores, fixtures, and group standings for the 2026 men's football tournament.
|
|
@@ -6648,7 +6866,7 @@ function buildServer() {
|
|
|
6648
6866
|
},
|
|
6649
6867
|
async (uri, variables) => {
|
|
6650
6868
|
const group = String(variables.group ?? "");
|
|
6651
|
-
const text = await standingsResourceText(group,
|
|
6869
|
+
const text = await standingsResourceText(group, resolveAdapter({}));
|
|
6652
6870
|
return { contents: [{ uri: uri.href, mimeType: "text/plain", text }] };
|
|
6653
6871
|
}
|
|
6654
6872
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claudinho/mcp",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"mcpName": "io.github.arturogarrido/claudinho",
|
|
5
5
|
"description": "World Cup MCP server for the 2026 men's football tournament — live scores, fixtures, standings, read-only prediction-market signals, and paste-ready match cards. Works with Claude Code, Cursor, Codex, Windsurf, Zed. Not affiliated with FIFA or Anthropic.",
|
|
6
6
|
"type": "module",
|
|
@@ -55,11 +55,11 @@
|
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/node": "^22.20.2",
|
|
58
|
-
"@vitest/coverage-v8": "^
|
|
58
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
59
59
|
"tsup": "^8.0.0",
|
|
60
60
|
"typescript": "^5.7.0",
|
|
61
|
-
"vitest": "^
|
|
62
|
-
"@claudinho/core": "0.10.
|
|
61
|
+
"vitest": "^5.0.0",
|
|
62
|
+
"@claudinho/core": "0.10.1"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "tsup",
|