@claudinho/mcp 0.9.4 → 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 +311 -83
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6
6
|
// src/server.ts
|
|
7
7
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
8
|
import { types as utilTypes } from "util";
|
|
9
|
-
import { z } from "zod";
|
|
9
|
+
import { z } from "zod/v3";
|
|
10
10
|
|
|
11
11
|
// ../core/dist/index.js
|
|
12
12
|
var REGIONAL_INDICATOR_A = 127462;
|
|
@@ -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,30 +3434,37 @@ 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
|
-
const
|
|
3444
|
+
const seenCodes = /* @__PURE__ */ new Map();
|
|
3429
3445
|
const seenRanks = /* @__PURE__ */ new Set();
|
|
3430
3446
|
const ranked = [];
|
|
3431
3447
|
for (const e of entries) {
|
|
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
|
-
const
|
|
3438
|
-
|
|
3454
|
+
const { providerId, providerRank } = r.value;
|
|
3455
|
+
const code = r.value.team.code;
|
|
3456
|
+
const priorHadId = seenCodes.get(code);
|
|
3457
|
+
const codeCollision = priorHadId !== void 0 && (providerId === void 0 || priorHadId === false);
|
|
3458
|
+
if (codeCollision || seenRanks.has(providerRank) || providerId !== void 0 && seenProviderIds.has(providerId)) {
|
|
3439
3459
|
complete = false;
|
|
3460
|
+
omitted += 1;
|
|
3440
3461
|
continue;
|
|
3441
3462
|
}
|
|
3442
|
-
|
|
3443
|
-
seenRanks.add(
|
|
3444
|
-
if (
|
|
3463
|
+
seenCodes.set(code, providerId !== void 0);
|
|
3464
|
+
seenRanks.add(providerRank);
|
|
3465
|
+
if (providerId !== void 0) seenProviderIds.add(providerId);
|
|
3445
3466
|
const { providerId: _dropId, providerRank: rank, ...row } = r.value;
|
|
3446
|
-
ranked.push({ row, rank });
|
|
3467
|
+
ranked.push({ row: { ...row, rank }, rank });
|
|
3447
3468
|
}
|
|
3448
3469
|
ranked.sort((a, b) => {
|
|
3449
3470
|
if (a.rank && b.rank && a.rank !== b.rank) return a.rank - b.rank;
|
|
@@ -3455,7 +3476,11 @@ function parseEspnStandings(raw) {
|
|
|
3455
3476
|
complete = false;
|
|
3456
3477
|
continue;
|
|
3457
3478
|
}
|
|
3458
|
-
out.push({
|
|
3479
|
+
out.push({
|
|
3480
|
+
group: letter,
|
|
3481
|
+
rows: ranked.map((x) => x.row),
|
|
3482
|
+
...omitted > 0 ? { partial: { omitted } } : {}
|
|
3483
|
+
});
|
|
3459
3484
|
}
|
|
3460
3485
|
return {
|
|
3461
3486
|
items: out,
|
|
@@ -3466,11 +3491,72 @@ function parseEspnStandings(raw) {
|
|
|
3466
3491
|
complete: complete && !rowsTruncated
|
|
3467
3492
|
};
|
|
3468
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
|
+
}
|
|
3469
3541
|
var ESPN_SOCCER = "https://site.api.espn.com/apis/site/v2/sports/soccer";
|
|
3470
3542
|
var DEFAULT_COMPETITION = "fifa.world";
|
|
3471
3543
|
var DEFAULT_BASE = `${ESPN_SOCCER}/${DEFAULT_COMPETITION}`;
|
|
3472
|
-
var USER_AGENT = `claudinho/${"0.
|
|
3544
|
+
var USER_AGENT = `claudinho/${"0.10.1"} (+https://github.com/arturogarrido/claudinho)`;
|
|
3473
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
|
+
}
|
|
3474
3560
|
function competitionBase(slug) {
|
|
3475
3561
|
return `${ESPN_SOCCER}/${slug}`;
|
|
3476
3562
|
}
|
|
@@ -3479,6 +3565,8 @@ var STANDINGS_SHARE_MS = 3e4;
|
|
|
3479
3565
|
var ProviderError = class extends Error {
|
|
3480
3566
|
kind;
|
|
3481
3567
|
status;
|
|
3568
|
+
/** For a throttle: how long the adapter will refuse to fetch (bounded). */
|
|
3569
|
+
retryAfterMs;
|
|
3482
3570
|
constructor(message, kind, status) {
|
|
3483
3571
|
super(message);
|
|
3484
3572
|
this.name = "ProviderError";
|
|
@@ -3502,6 +3590,7 @@ function usableProviderItems(kind, parsed, hasUsableRecord = parsed.items.length
|
|
|
3502
3590
|
var EspnAdapter = class {
|
|
3503
3591
|
constructor(opts = {}) {
|
|
3504
3592
|
this.opts = opts;
|
|
3593
|
+
this.clock = opts.now ?? (() => Date.now());
|
|
3505
3594
|
const expected = opts.expectedStandingsGroups ?? (opts.baseUrl === void 0 ? groups() : void 0);
|
|
3506
3595
|
this.expectedStandingsGroups = expected ? [...expected] : void 0;
|
|
3507
3596
|
this.standingsFallbackGroups = opts.baseUrl === void 0 && expected ? [...expected] : void 0;
|
|
@@ -3527,6 +3616,53 @@ var EspnAdapter = class {
|
|
|
3527
3616
|
* throttle (persist a backoff) from an ordinary blip.
|
|
3528
3617
|
*/
|
|
3529
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
|
+
}
|
|
3530
3666
|
async fetchByDate(dateISO) {
|
|
3531
3667
|
return this.fetchScoreboard(toEspnDate(dateISO));
|
|
3532
3668
|
}
|
|
@@ -3614,6 +3750,11 @@ var EspnAdapter = class {
|
|
|
3614
3750
|
return usableProviderItems("scoreboard", parsed);
|
|
3615
3751
|
}
|
|
3616
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
|
+
}
|
|
3617
3758
|
const doFetch = this.opts.fetchImpl ?? fetch;
|
|
3618
3759
|
const controller = new AbortController();
|
|
3619
3760
|
const timer = setTimeout(
|
|
@@ -3635,19 +3776,24 @@ var EspnAdapter = class {
|
|
|
3635
3776
|
throw e?.name === "AbortError" ? new ProviderError(`ESPN request timed out: ${url}`, "timeout") : new ProviderError(`ESPN request failed: ${e?.message ?? e}`, "http");
|
|
3636
3777
|
}
|
|
3637
3778
|
if (!res.ok) {
|
|
3638
|
-
|
|
3779
|
+
const pe = new ProviderError(
|
|
3639
3780
|
`ESPN request failed: ${res.status} ${res.statusText}`,
|
|
3640
3781
|
"http",
|
|
3641
3782
|
res.status
|
|
3642
3783
|
);
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
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;
|
|
3647
3790
|
}
|
|
3648
3791
|
try {
|
|
3649
|
-
return await res
|
|
3792
|
+
return await readJsonBounded(res, MAX_RESPONSE_BYTES);
|
|
3650
3793
|
} catch (e) {
|
|
3794
|
+
if (e instanceof ResponseTooLargeError) {
|
|
3795
|
+
throw new ProviderError(`ESPN response too large: ${e.bytes} bytes`, "parse");
|
|
3796
|
+
}
|
|
3651
3797
|
throw new ProviderError(
|
|
3652
3798
|
`ESPN response unparseable: ${e?.message ?? e}`,
|
|
3653
3799
|
"parse"
|
|
@@ -3690,14 +3836,30 @@ function hasGroupStarted(group, tables) {
|
|
|
3690
3836
|
function matchesPerTeamInGroup(teamCount) {
|
|
3691
3837
|
return Math.max(0, teamCount - 1);
|
|
3692
3838
|
}
|
|
3693
|
-
function isGroupStandingsComplete(table) {
|
|
3694
|
-
|
|
3839
|
+
function isGroupStandingsComplete(table, expectedTeams) {
|
|
3840
|
+
if (!table || table.partial) return false;
|
|
3841
|
+
const n = table.rows.length;
|
|
3695
3842
|
if (n < 2) return false;
|
|
3696
|
-
|
|
3843
|
+
if (expectedTeams !== void 0 && n < expectedTeams) return false;
|
|
3844
|
+
const required = matchesPerTeamInGroup(Math.max(n, expectedTeams ?? n));
|
|
3697
3845
|
return table.rows.every((r) => r.played >= required);
|
|
3698
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
|
+
}
|
|
3699
3855
|
function isGroupComplete(group, tables) {
|
|
3700
|
-
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;
|
|
3701
3863
|
}
|
|
3702
3864
|
function resolveWinner(match) {
|
|
3703
3865
|
if (!isFinished(match.status)) return void 0;
|
|
@@ -3752,7 +3914,7 @@ function resolveSlot(ref, ctx, liveTeam, fixtureInMergedSet = false) {
|
|
|
3752
3914
|
return tbd(ref.label);
|
|
3753
3915
|
case "group": {
|
|
3754
3916
|
if (liveParticipant) return liveParticipant;
|
|
3755
|
-
if (!ctx.standingsDegraded && hasGroupStarted(ref.group, ctx.tables)) {
|
|
3917
|
+
if (!ctx.standingsDegraded && !isGroupPartial(ref.group, ctx.tables) && hasGroupStarted(ref.group, ctx.tables)) {
|
|
3756
3918
|
const team = teamFromStandings(ref.group, ref.position, ctx.tables);
|
|
3757
3919
|
if (team) {
|
|
3758
3920
|
const status = isGroupComplete(ref.group, ctx.tables) ? "confirmed" : "projected";
|
|
@@ -4386,13 +4548,17 @@ function resolveCompetition(explicit) {
|
|
|
4386
4548
|
}
|
|
4387
4549
|
return DEFAULT_COMPETITION;
|
|
4388
4550
|
}
|
|
4551
|
+
var BUNDLE_COMPETITION = DEFAULT_COMPETITION;
|
|
4552
|
+
function bundleApplies(competition = resolveCompetition()) {
|
|
4553
|
+
return competition === BUNDLE_COMPETITION;
|
|
4554
|
+
}
|
|
4389
4555
|
var KNOWN_SOURCES = ["espn"];
|
|
4390
|
-
function makeAdapter(source = "espn") {
|
|
4556
|
+
function makeAdapter(source = "espn", opts = {}) {
|
|
4391
4557
|
switch (source) {
|
|
4392
4558
|
case "espn": {
|
|
4393
4559
|
const competition = resolveCompetition();
|
|
4394
4560
|
const baseUrl = competition === DEFAULT_COMPETITION ? void 0 : competitionBase(competition);
|
|
4395
|
-
return new EspnAdapter({ baseUrl });
|
|
4561
|
+
return new EspnAdapter({ baseUrl, enrichGroups: opts.enrichGroups, now: opts.now });
|
|
4396
4562
|
}
|
|
4397
4563
|
default:
|
|
4398
4564
|
throw new Error(
|
|
@@ -4410,7 +4576,7 @@ function liveSourceLabel(source) {
|
|
|
4410
4576
|
return known[source] ?? source.charAt(0).toUpperCase() + source.slice(1);
|
|
4411
4577
|
}
|
|
4412
4578
|
async function getMatchesForDate(adapter, dateISO) {
|
|
4413
|
-
const base = allFixtures();
|
|
4579
|
+
const base = bundleApplies() ? allFixtures() : [];
|
|
4414
4580
|
const day = dateISO.slice(0, 10);
|
|
4415
4581
|
try {
|
|
4416
4582
|
const live = adapter.fetchWindow ? await adapter.fetchWindow(shiftUtcDate(day, -1), shiftUtcDate(day, 1)) : await adapter.fetchByDate(day);
|
|
@@ -4455,21 +4621,27 @@ function knockoutWindow() {
|
|
|
4455
4621
|
return knockoutWindowMemo;
|
|
4456
4622
|
}
|
|
4457
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
|
+
}
|
|
4458
4628
|
const topology = loadBracketTopology();
|
|
4459
4629
|
const base = allFixtures().filter((m) => m.stage !== "GROUP" && m.stage !== "FRIENDLY");
|
|
4460
4630
|
let matches = base;
|
|
4461
4631
|
let liveDegraded = true;
|
|
4462
4632
|
let source;
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
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
|
+
}
|
|
4470
4642
|
}
|
|
4471
4643
|
const standings = await getStandings(adapter);
|
|
4472
|
-
if (!source && !standings.degraded && standings.source) {
|
|
4644
|
+
if (!source && !standings.degraded && standings.source && standings.tables.length > 0) {
|
|
4473
4645
|
source = standings.source;
|
|
4474
4646
|
}
|
|
4475
4647
|
const view = buildBracketView(
|
|
@@ -4491,18 +4663,18 @@ async function getBracket(adapter, opts = {}) {
|
|
|
4491
4663
|
}
|
|
4492
4664
|
var EXTRA_TIME_SLACK_MS = 60 * 6e4;
|
|
4493
4665
|
async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Date()) {
|
|
4666
|
+
if (!bundleApplies()) return { match: void 0, degraded: false, unsupported: true };
|
|
4494
4667
|
const nowMs = now.getTime();
|
|
4495
4668
|
let fixtures = allFixtures();
|
|
4496
4669
|
let overlayFailed = false;
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
fixtures = mergeLive(
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
);
|
|
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;
|
|
4504
4676
|
}
|
|
4505
|
-
}
|
|
4677
|
+
} else {
|
|
4506
4678
|
overlayFailed = true;
|
|
4507
4679
|
}
|
|
4508
4680
|
const candidate = fixturesByTeam(code, fixtures).find((m) => {
|
|
@@ -4518,23 +4690,27 @@ async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Dat
|
|
|
4518
4690
|
return { match: next, degraded: overlayFailed };
|
|
4519
4691
|
}
|
|
4520
4692
|
async function getNextFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Date()) {
|
|
4693
|
+
if (!bundleApplies()) return { fixture: void 0, degraded: false, unsupported: true };
|
|
4521
4694
|
const base = allFixtures();
|
|
4522
4695
|
let matches = base;
|
|
4523
4696
|
let degraded = true;
|
|
4524
4697
|
let liveById;
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
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
|
+
}
|
|
4532
4707
|
}
|
|
4533
4708
|
const fixture = nextFixtureForTeam(code, { from: now, fixtures: matches });
|
|
4534
4709
|
const source = fixture && liveById?.has(fixture.id) ? adapter.name : void 0;
|
|
4535
4710
|
return { fixture, degraded, source };
|
|
4536
4711
|
}
|
|
4537
4712
|
async function getMatchById(adapter, id) {
|
|
4713
|
+
if (!bundleApplies()) return { match: void 0, degraded: false, unsupported: true };
|
|
4538
4714
|
const base = allFixtures().find((m) => m.id === id);
|
|
4539
4715
|
if (!base) return { match: void 0, degraded: false };
|
|
4540
4716
|
const day = base.kickoff.slice(0, 10);
|
|
@@ -4972,11 +5148,15 @@ var PolymarketProvider = class {
|
|
|
4972
5148
|
if (!res.ok) {
|
|
4973
5149
|
throw new Error(`Polymarket request failed: ${res.status} ${res.statusText}`);
|
|
4974
5150
|
}
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
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;
|
|
4978
5159
|
}
|
|
4979
|
-
const data = await res.json();
|
|
4980
5160
|
if (Array.isArray(data) && data.length > 1) {
|
|
4981
5161
|
return ambiguous("slug returned more than one event");
|
|
4982
5162
|
}
|
|
@@ -5237,6 +5417,10 @@ function numberish(v) {
|
|
|
5237
5417
|
}
|
|
5238
5418
|
return void 0;
|
|
5239
5419
|
}
|
|
5420
|
+
var MARKET_COMPETITIONS = /* @__PURE__ */ new Set([DEFAULT_COMPETITION]);
|
|
5421
|
+
function marketsCoverCompetition(competition = resolveCompetition()) {
|
|
5422
|
+
return MARKET_COMPETITIONS.has(competition);
|
|
5423
|
+
}
|
|
5240
5424
|
function resolveMarketSource(explicit) {
|
|
5241
5425
|
if (explicit) return explicit;
|
|
5242
5426
|
if (typeof process !== "undefined" && process.env?.CLAUDINHO_MARKETS_SOURCE) {
|
|
@@ -5253,6 +5437,7 @@ function makeMarketProvider(source) {
|
|
|
5253
5437
|
return new FakeMarketProvider();
|
|
5254
5438
|
// no synth → yields no signals, no network
|
|
5255
5439
|
default:
|
|
5440
|
+
if (!marketsCoverCompetition()) return new FakeMarketProvider();
|
|
5256
5441
|
return new PolymarketProvider();
|
|
5257
5442
|
}
|
|
5258
5443
|
}
|
|
@@ -5378,10 +5563,13 @@ function formatShareTable(input, options = {}) {
|
|
|
5378
5563
|
input.emptyNote ?? (input.degraded ? "Live standings unavailable." : "No standings available.")
|
|
5379
5564
|
);
|
|
5380
5565
|
} else {
|
|
5381
|
-
for (const { group, rows } of input.tables) {
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
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"));
|
|
5385
5573
|
}
|
|
5386
5574
|
if (input.degraded) {
|
|
5387
5575
|
blocks.push("(Live standings unavailable \u2014 group roster, not live results.)");
|
|
@@ -5584,7 +5772,9 @@ function marketText(m, sig, args) {
|
|
|
5584
5772
|
return `${marketHeader(m, args)}
|
|
5585
5773
|
${marketBlock(sig, m).join("\n")}`;
|
|
5586
5774
|
}
|
|
5775
|
+
var MARKETS_SCOPE_NOTE = "Market signals cover the World Cup only; none are read for this competition.";
|
|
5587
5776
|
function noSignalText(m, args, now) {
|
|
5777
|
+
if (!marketsCoverCompetition()) return `${marketHeader(m, args)} \u2014 ${MARKETS_SCOPE_NOTE}`;
|
|
5588
5778
|
if (marketRelevant(m, now)) return `No reliable market signal for ${marketHeader(m, args)}.`;
|
|
5589
5779
|
const verb = isFinished(m.status) ? "has finished" : "appears to have finished";
|
|
5590
5780
|
return `${marketHeader(m, args)} ${verb} \u2014 market signals are pre-match and in-play reads.`;
|
|
@@ -5744,9 +5934,19 @@ ${matchList(matches, "No matches in play right now.", opts)}`;
|
|
|
5744
5934
|
};
|
|
5745
5935
|
}
|
|
5746
5936
|
async function toolGetMatch(args) {
|
|
5747
|
-
const { match, degraded, source: liveSource } = await getMatchById(
|
|
5937
|
+
const { match, degraded, source: liveSource, unsupported } = await getMatchById(
|
|
5938
|
+
resolveAdapter(args),
|
|
5939
|
+
args.id
|
|
5940
|
+
);
|
|
5748
5941
|
if (!match) {
|
|
5749
|
-
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
|
+
};
|
|
5750
5950
|
}
|
|
5751
5951
|
const opts = fmtOpts(args);
|
|
5752
5952
|
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
@@ -5779,7 +5979,11 @@ ${marketBlock(marketSignal, match).join("\n")}` : base;
|
|
|
5779
5979
|
async function toolGetStandings(args) {
|
|
5780
5980
|
const { tables, degraded, source } = await getStandings(resolveAdapter(args), args.group);
|
|
5781
5981
|
const boundedTables = boundedRecords(tables);
|
|
5782
|
-
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
|
+
}));
|
|
5783
5987
|
if (shaped.length === 0) {
|
|
5784
5988
|
const g = args.group?.toUpperCase();
|
|
5785
5989
|
const msg = degraded ? t(args.lang, "standings.unavailable") : g ? `No group "${g}".` : "No standings available.";
|
|
@@ -5788,7 +5992,11 @@ async function toolGetStandings(args) {
|
|
|
5788
5992
|
data: { degraded, source: source ?? null, tables: args.group ? null : [] }
|
|
5789
5993
|
};
|
|
5790
5994
|
}
|
|
5791
|
-
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");
|
|
5792
6000
|
text += truncationNote(boundedTables);
|
|
5793
6001
|
if (degraded) text += "\n\n(Live standings unavailable \u2014 showing the group roster.)";
|
|
5794
6002
|
return {
|
|
@@ -5809,10 +6017,16 @@ async function toolGetBracket(args) {
|
|
|
5809
6017
|
data: { view: null }
|
|
5810
6018
|
};
|
|
5811
6019
|
}
|
|
5812
|
-
const { view, degraded, standingsDegraded, source } = await getBracket(
|
|
6020
|
+
const { view, degraded, standingsDegraded, source, unsupported } = await getBracket(
|
|
5813
6021
|
resolveAdapter(args),
|
|
5814
6022
|
filter ? { stage: filter, lang: args.lang } : { lang: args.lang }
|
|
5815
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
|
+
}
|
|
5816
6030
|
let text = formatBracketList(view, { footer: false, locale: args.lang, tz: args.tz });
|
|
5817
6031
|
if (degraded) {
|
|
5818
6032
|
text += `
|
|
@@ -5833,18 +6047,20 @@ async function standingsResourceText(group, adapter) {
|
|
|
5833
6047
|
const { tables, degraded, source } = await getStandings(adapter, g);
|
|
5834
6048
|
const tb = tables[0];
|
|
5835
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) })})`;
|
|
5836
6052
|
if (degraded && tb) text += "\n\n(Live standings unavailable \u2014 showing the group roster.)";
|
|
5837
6053
|
return withDisclaimer(text, source);
|
|
5838
6054
|
}
|
|
5839
6055
|
async function toolGetNextFixture(args) {
|
|
5840
6056
|
const code = args.team.toUpperCase();
|
|
5841
|
-
const { fixture, degraded, source } = await getNextFixtureForTeam(
|
|
6057
|
+
const { fixture, degraded, source, unsupported } = await getNextFixtureForTeam(
|
|
5842
6058
|
resolveAdapter(args),
|
|
5843
6059
|
code,
|
|
5844
6060
|
args.now ?? /* @__PURE__ */ new Date()
|
|
5845
6061
|
);
|
|
5846
6062
|
if (!fixture) {
|
|
5847
|
-
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}.`;
|
|
5848
6064
|
return {
|
|
5849
6065
|
text: withDisclaimer(msg, void 0, args.lang),
|
|
5850
6066
|
data: { team: code, fixture: null, degraded, source: source ?? null }
|
|
@@ -5876,12 +6092,12 @@ async function toolGetMarketSignal(args) {
|
|
|
5876
6092
|
const provider = resolveMarketProvider(args);
|
|
5877
6093
|
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
5878
6094
|
if (args.matchId) {
|
|
5879
|
-
const { match } = await getMatchById(resolveAdapter(args), args.matchId);
|
|
6095
|
+
const { match, unsupported } = await getMatchById(resolveAdapter(args), args.matchId);
|
|
5880
6096
|
const relevant = match ? marketRelevant(match, now) : false;
|
|
5881
6097
|
const batch2 = match && relevant ? await getMarketSignals(provider, [match], MARKETS_TOOL_OPTS) : { results: /* @__PURE__ */ new Map(), complete: true };
|
|
5882
6098
|
const sig = match ? resolvedValues(batch2).get(match.id) : void 0;
|
|
5883
6099
|
const shown2 = batch2.complete && match && sig && marketDisplayable(match, sig) ? sig : void 0;
|
|
5884
|
-
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);
|
|
5885
6101
|
return {
|
|
5886
6102
|
text: withDisclaimer(text2),
|
|
5887
6103
|
data: {
|
|
@@ -5894,12 +6110,16 @@ async function toolGetMarketSignal(args) {
|
|
|
5894
6110
|
}
|
|
5895
6111
|
if (args.team) {
|
|
5896
6112
|
const code = args.team.toUpperCase();
|
|
5897
|
-
const { match: fixture, degraded } = await marketFixtureForTeam(
|
|
6113
|
+
const { match: fixture, degraded, unsupported } = await marketFixtureForTeam(
|
|
6114
|
+
resolveAdapter(args),
|
|
6115
|
+
code,
|
|
6116
|
+
now
|
|
6117
|
+
);
|
|
5898
6118
|
const relevant = fixture ? marketRelevant(fixture, now) : false;
|
|
5899
6119
|
const batch2 = fixture && relevant ? await getMarketSignals(provider, [fixture], MARKETS_TOOL_OPTS) : { results: /* @__PURE__ */ new Map(), complete: true };
|
|
5900
6120
|
const sig = fixture ? resolvedValues(batch2).get(fixture.id) : void 0;
|
|
5901
6121
|
const shown2 = batch2.complete && fixture && sig && marketDisplayable(fixture, sig) ? sig : void 0;
|
|
5902
|
-
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);
|
|
5903
6123
|
return {
|
|
5904
6124
|
text: withDisclaimer(text2),
|
|
5905
6125
|
data: {
|
|
@@ -5929,7 +6149,7 @@ ${shown.items.map(({ match, signal }) => marketText(match, signal, args)).join("
|
|
|
5929
6149
|
// so "we could not reach the market data" rendered as the confident
|
|
5930
6150
|
// "there is none", which is the failure this project refuses everywhere
|
|
5931
6151
|
// else.
|
|
5932
|
-
batch.complete ? `No reliable market signals on ${date}.` : `Market data unavailable or incomplete for ${date} \u2014 not all fixtures could be checked.`
|
|
6152
|
+
!marketsCoverCompetition() ? `${MARKETS_SCOPE_NOTE} (${date})` : batch.complete ? `No reliable market signals on ${date}.` : `Market data unavailable or incomplete for ${date} \u2014 not all fixtures could be checked.`
|
|
5933
6153
|
);
|
|
5934
6154
|
if (shown.shown > 0 && !batch.complete) {
|
|
5935
6155
|
text += `
|
|
@@ -6059,7 +6279,12 @@ async function toolGetShareSnippet(args) {
|
|
|
6059
6279
|
degraded: degraded2,
|
|
6060
6280
|
informationalOnly: true,
|
|
6061
6281
|
snippet,
|
|
6062
|
-
|
|
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
|
+
}))
|
|
6063
6288
|
}
|
|
6064
6289
|
};
|
|
6065
6290
|
}
|
|
@@ -6075,7 +6300,7 @@ async function toolGetShareSnippet(args) {
|
|
|
6075
6300
|
data: { kind: "bracket", view: null }
|
|
6076
6301
|
};
|
|
6077
6302
|
}
|
|
6078
|
-
const { view, degraded: degraded2, source: source2 } = await getBracket(
|
|
6303
|
+
const { view, degraded: degraded2, source: source2, unsupported } = await getBracket(
|
|
6079
6304
|
resolveAdapter(args),
|
|
6080
6305
|
stageFilter ? { stage: stageFilter, lang: args.lang } : { lang: args.lang }
|
|
6081
6306
|
);
|
|
@@ -6084,7 +6309,7 @@ async function toolGetShareSnippet(args) {
|
|
|
6084
6309
|
view,
|
|
6085
6310
|
source: degraded2 ? void 0 : source2,
|
|
6086
6311
|
installLine: stageFilter ? `npx @claudinho/cli bracket ${stageFilter}` : "npx @claudinho/cli bracket",
|
|
6087
|
-
emptyNote: t(args.lang, "bracket.empty")
|
|
6312
|
+
emptyNote: unsupported ? t(args.lang, "competition.unsupported") : t(args.lang, "bracket.empty")
|
|
6088
6313
|
},
|
|
6089
6314
|
{ ...options, locale: args.lang, tz: args.tz }
|
|
6090
6315
|
);
|
|
@@ -6103,7 +6328,10 @@ async function toolGetShareSnippet(args) {
|
|
|
6103
6328
|
};
|
|
6104
6329
|
}
|
|
6105
6330
|
if (args.matchId) {
|
|
6106
|
-
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
|
+
);
|
|
6107
6335
|
const matches = match ? [match] : [];
|
|
6108
6336
|
const market2 = await signalsFor(matches);
|
|
6109
6337
|
return shareResult(
|
|
@@ -6117,7 +6345,7 @@ async function toolGetShareSnippet(args) {
|
|
|
6117
6345
|
marketComplete: market2.complete,
|
|
6118
6346
|
source: source2,
|
|
6119
6347
|
degraded: degraded2,
|
|
6120
|
-
emptyNote: `No match found with id ${args.matchId}.`,
|
|
6348
|
+
emptyNote: unsupported ? t(args.lang, "competition.unsupported") : `No match found with id ${args.matchId}.`,
|
|
6121
6349
|
installLine: `npx @claudinho/cli match ${args.matchId}`,
|
|
6122
6350
|
tz: args.tz,
|
|
6123
6351
|
locale: args.lang
|
|
@@ -6127,7 +6355,7 @@ async function toolGetShareSnippet(args) {
|
|
|
6127
6355
|
}
|
|
6128
6356
|
if (args.team) {
|
|
6129
6357
|
const code = args.team.toUpperCase();
|
|
6130
|
-
const { fixture, degraded: degraded2, source: source2 } = await getNextFixtureForTeam(
|
|
6358
|
+
const { fixture, degraded: degraded2, source: source2, unsupported } = await getNextFixtureForTeam(
|
|
6131
6359
|
resolveAdapter(args),
|
|
6132
6360
|
code,
|
|
6133
6361
|
args.now ?? /* @__PURE__ */ new Date()
|
|
@@ -6148,7 +6376,7 @@ async function toolGetShareSnippet(args) {
|
|
|
6148
6376
|
// with get_next_fixture (a static group fixture carries no source).
|
|
6149
6377
|
source: source2,
|
|
6150
6378
|
degraded: degraded2,
|
|
6151
|
-
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}.`,
|
|
6152
6380
|
installLine: `npx @claudinho/cli next ${code}`,
|
|
6153
6381
|
tz: args.tz,
|
|
6154
6382
|
locale: args.lang
|
|
@@ -6187,7 +6415,7 @@ async function toolGetShareSnippet(args) {
|
|
|
6187
6415
|
|
|
6188
6416
|
// src/server.ts
|
|
6189
6417
|
var SERVER_NAME = "claudinho";
|
|
6190
|
-
var SERVER_VERSION = "0.
|
|
6418
|
+
var SERVER_VERSION = "0.10.1";
|
|
6191
6419
|
var VOICE = asFlavorLevel(process.env.CLAUDINHO_FLAVOR) === "off" ? "" : `
|
|
6192
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.`;
|
|
6193
6421
|
var INSTRUCTIONS = `Claudinho serves live scores, fixtures, and group standings for the 2026 men's football tournament.
|
|
@@ -6638,7 +6866,7 @@ function buildServer() {
|
|
|
6638
6866
|
},
|
|
6639
6867
|
async (uri, variables) => {
|
|
6640
6868
|
const group = String(variables.group ?? "");
|
|
6641
|
-
const text = await standingsResourceText(group,
|
|
6869
|
+
const text = await standingsResourceText(group, resolveAdapter({}));
|
|
6642
6870
|
return { contents: [{ uri: uri.href, mimeType: "text/plain", text }] };
|
|
6643
6871
|
}
|
|
6644
6872
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claudinho/mcp",
|
|
3
|
-
"version": "0.
|
|
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",
|
|
@@ -51,15 +51,15 @@
|
|
|
51
51
|
],
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
54
|
-
"zod": "^
|
|
54
|
+
"zod": "^4.6.2"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@types/node": "^22.20.
|
|
58
|
-
"@vitest/coverage-v8": "^
|
|
57
|
+
"@types/node": "^22.20.2",
|
|
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.
|
|
61
|
+
"vitest": "^5.0.0",
|
|
62
|
+
"@claudinho/core": "0.10.1"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "tsup",
|