@claudinho/cli 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 +613 -179
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -274,6 +274,8 @@ var EN = {
|
|
|
274
274
|
"bracket.slot.tbd": "TBD",
|
|
275
275
|
"live.data": "Live data: {source}",
|
|
276
276
|
"standings.unavailable": "Live standings unavailable.",
|
|
277
|
+
"standings.partial": "Partial table \u2014 {n} rows could not be read.",
|
|
278
|
+
"competition.unsupported": "Not available for this competition yet.",
|
|
277
279
|
"share.tryIt": "Try it: {line}",
|
|
278
280
|
"stage.group": "Group {group}",
|
|
279
281
|
"stage.groupStage": "Group stage",
|
|
@@ -304,6 +306,8 @@ var ES = {
|
|
|
304
306
|
"bracket.slot.tbd": "Por definir",
|
|
305
307
|
"live.data": "Datos en vivo: {source}",
|
|
306
308
|
"standings.unavailable": "Tabla en vivo no disponible.",
|
|
309
|
+
"standings.partial": "Tabla parcial \u2014 no se pudieron leer {n} filas.",
|
|
310
|
+
"competition.unsupported": "A\xFAn no disponible para esta competici\xF3n.",
|
|
307
311
|
"share.tryIt": "Pru\xE9balo: {line}",
|
|
308
312
|
"stage.group": "Grupo {group}",
|
|
309
313
|
"stage.groupStage": "Fase de grupos",
|
|
@@ -334,6 +338,8 @@ var PT = {
|
|
|
334
338
|
"bracket.slot.tbd": "A definir",
|
|
335
339
|
"live.data": "Dados ao vivo: {source}",
|
|
336
340
|
"standings.unavailable": "Classifica\xE7\xE3o ao vivo indispon\xEDvel.",
|
|
341
|
+
"standings.partial": "Tabela parcial \u2014 {n} linhas n\xE3o puderam ser lidas.",
|
|
342
|
+
"competition.unsupported": "Ainda n\xE3o dispon\xEDvel para esta competi\xE7\xE3o.",
|
|
337
343
|
"share.tryIt": "Experimente: {line}",
|
|
338
344
|
"stage.group": "Grupo {group}",
|
|
339
345
|
"stage.groupStage": "Fase de grupos",
|
|
@@ -364,6 +370,8 @@ var FR = {
|
|
|
364
370
|
"bracket.slot.tbd": "\xC0 d\xE9finir",
|
|
365
371
|
"live.data": "Donn\xE9es en direct : {source}",
|
|
366
372
|
"standings.unavailable": "Classement en direct indisponible.",
|
|
373
|
+
"standings.partial": "Classement partiel \u2014 {n} lignes n'ont pas pu \xEAtre lues.",
|
|
374
|
+
"competition.unsupported": "Pas encore disponible pour cette comp\xE9tition.",
|
|
367
375
|
"share.tryIt": "Essayez : {line}",
|
|
368
376
|
"stage.group": "Groupe {group}",
|
|
369
377
|
"stage.groupStage": "Phase de groupes",
|
|
@@ -2848,11 +2856,13 @@ function fixturesByGroup(group, fixtures = SCHEDULE) {
|
|
|
2848
2856
|
const g = group.toUpperCase();
|
|
2849
2857
|
return fixtures.filter((m) => (m.group ?? "").toUpperCase() === g).sort(byKickoff);
|
|
2850
2858
|
}
|
|
2859
|
+
function isUpcoming(m, now = /* @__PURE__ */ new Date()) {
|
|
2860
|
+
if (m.status === "CANCELLED" || m.status === "POSTPONED") return false;
|
|
2861
|
+
return Date.parse(m.kickoff) >= now.getTime();
|
|
2862
|
+
}
|
|
2851
2863
|
function nextFixtureForTeam(code, opts = {}) {
|
|
2852
2864
|
const from = opts.from ?? /* @__PURE__ */ new Date();
|
|
2853
|
-
return fixturesByTeam(code, opts.fixtures ?? SCHEDULE).find(
|
|
2854
|
-
(m) => new Date(m.kickoff).getTime() >= from.getTime()
|
|
2855
|
-
);
|
|
2865
|
+
return fixturesByTeam(code, opts.fixtures ?? SCHEDULE).find((m) => isUpcoming(m, from));
|
|
2856
2866
|
}
|
|
2857
2867
|
var LIVE_WINDOW_MS = 140 * 6e4;
|
|
2858
2868
|
var KNOCKOUT_EXTRA_TIME_MS = 60 * 6e4;
|
|
@@ -2980,8 +2990,8 @@ function humanLabel(value, maxColumns = MAX_LABEL_COLUMNS) {
|
|
|
2980
2990
|
const capped = value.length > MAX_LABEL_INPUT_UNITS ? value.slice(0, MAX_LABEL_INPUT_UNITS) : value;
|
|
2981
2991
|
return visible(runToFixedPoint(capped, maxColumns));
|
|
2982
2992
|
}
|
|
2983
|
-
function visible(
|
|
2984
|
-
return
|
|
2993
|
+
function visible(label2) {
|
|
2994
|
+
return label2 !== "" && displayWidth(label2) === 0 ? "" : label2;
|
|
2985
2995
|
}
|
|
2986
2996
|
function runToFixedPoint(capped, maxColumns) {
|
|
2987
2997
|
const first = sealLabelOnce(capped, maxColumns);
|
|
@@ -3209,7 +3219,7 @@ function toParticipant(raw) {
|
|
|
3209
3219
|
const providerId = opaqueId(raw.team?.id, ESPN_ID);
|
|
3210
3220
|
const known = productFlag(name) !== nationToFlag("");
|
|
3211
3221
|
return valid(
|
|
3212
|
-
providerId && known ? { kind: "team", providerId, team } : { kind: "slot", team }
|
|
3222
|
+
providerId && known ? { kind: "team", providerId, team } : { kind: "slot", ...providerId ? { providerId } : {}, team }
|
|
3213
3223
|
);
|
|
3214
3224
|
}
|
|
3215
3225
|
function mapStatus(st) {
|
|
@@ -3278,7 +3288,7 @@ function parseEspnEvent(raw, ctx = {}) {
|
|
|
3278
3288
|
if (awayP.kind !== "valid") return awayP;
|
|
3279
3289
|
const h = homeP.value;
|
|
3280
3290
|
const a = awayP.value;
|
|
3281
|
-
if (h.
|
|
3291
|
+
if (h.providerId !== void 0 && h.providerId === a.providerId) {
|
|
3282
3292
|
return definitiveNone("both competitors are the same team");
|
|
3283
3293
|
}
|
|
3284
3294
|
const home = homeP.value.team;
|
|
@@ -3430,9 +3440,13 @@ function parseEspnStandings(raw) {
|
|
|
3430
3440
|
const seenGroups = /* @__PURE__ */ new Set();
|
|
3431
3441
|
const seenProviderIds = /* @__PURE__ */ new Set();
|
|
3432
3442
|
for (const child of children) {
|
|
3433
|
-
const
|
|
3434
|
-
const letter =
|
|
3435
|
-
if (!letter)
|
|
3443
|
+
const label2 = humanLabel(child?.name ?? child?.abbreviation);
|
|
3444
|
+
const letter = label2.match(/Group\s+([A-L])(?![A-Za-z0-9])/i)?.[1]?.toUpperCase();
|
|
3445
|
+
if (!letter) {
|
|
3446
|
+
const rows = child?.standings?.entries;
|
|
3447
|
+
if (Array.isArray(rows) && rows.length > 0) complete = false;
|
|
3448
|
+
continue;
|
|
3449
|
+
}
|
|
3436
3450
|
if (seenGroups.has(letter)) {
|
|
3437
3451
|
complete = false;
|
|
3438
3452
|
continue;
|
|
@@ -3443,9 +3457,11 @@ function parseEspnStandings(raw) {
|
|
|
3443
3457
|
complete = false;
|
|
3444
3458
|
continue;
|
|
3445
3459
|
}
|
|
3460
|
+
let omitted = 0;
|
|
3446
3461
|
if (rawEntries.length > MAX_GROUP_ROWS) {
|
|
3447
3462
|
rowsTruncated = true;
|
|
3448
3463
|
complete = false;
|
|
3464
|
+
omitted += rawEntries.length - MAX_GROUP_ROWS;
|
|
3449
3465
|
}
|
|
3450
3466
|
const entries = takeBounded(rawEntries, MAX_GROUP_ROWS);
|
|
3451
3467
|
const seenCodes = /* @__PURE__ */ new Map();
|
|
@@ -3455,6 +3471,7 @@ function parseEspnStandings(raw) {
|
|
|
3455
3471
|
const r = entryToRow(e);
|
|
3456
3472
|
if (r.kind !== "valid") {
|
|
3457
3473
|
if (r.kind !== "definitive-none") complete = false;
|
|
3474
|
+
omitted += 1;
|
|
3458
3475
|
continue;
|
|
3459
3476
|
}
|
|
3460
3477
|
const { providerId, providerRank } = r.value;
|
|
@@ -3463,13 +3480,14 @@ function parseEspnStandings(raw) {
|
|
|
3463
3480
|
const codeCollision = priorHadId !== void 0 && (providerId === void 0 || priorHadId === false);
|
|
3464
3481
|
if (codeCollision || seenRanks.has(providerRank) || providerId !== void 0 && seenProviderIds.has(providerId)) {
|
|
3465
3482
|
complete = false;
|
|
3483
|
+
omitted += 1;
|
|
3466
3484
|
continue;
|
|
3467
3485
|
}
|
|
3468
3486
|
seenCodes.set(code, providerId !== void 0);
|
|
3469
3487
|
seenRanks.add(providerRank);
|
|
3470
3488
|
if (providerId !== void 0) seenProviderIds.add(providerId);
|
|
3471
3489
|
const { providerId: _dropId, providerRank: rank, ...row } = r.value;
|
|
3472
|
-
ranked.push({ row, rank });
|
|
3490
|
+
ranked.push({ row: { ...row, rank }, rank });
|
|
3473
3491
|
}
|
|
3474
3492
|
ranked.sort((a, b) => {
|
|
3475
3493
|
if (a.rank && b.rank && a.rank !== b.rank) return a.rank - b.rank;
|
|
@@ -3481,7 +3499,11 @@ function parseEspnStandings(raw) {
|
|
|
3481
3499
|
complete = false;
|
|
3482
3500
|
continue;
|
|
3483
3501
|
}
|
|
3484
|
-
out2.push({
|
|
3502
|
+
out2.push({
|
|
3503
|
+
group: letter,
|
|
3504
|
+
rows: ranked.map((x) => x.row),
|
|
3505
|
+
...omitted > 0 ? { partial: { omitted } } : {}
|
|
3506
|
+
});
|
|
3485
3507
|
}
|
|
3486
3508
|
return {
|
|
3487
3509
|
items: out2,
|
|
@@ -3492,11 +3514,72 @@ function parseEspnStandings(raw) {
|
|
|
3492
3514
|
complete: complete && !rowsTruncated
|
|
3493
3515
|
};
|
|
3494
3516
|
}
|
|
3517
|
+
var ResponseTooLargeError = class extends Error {
|
|
3518
|
+
constructor(bytes, limit) {
|
|
3519
|
+
super(`response body exceeds ${limit} bytes (${bytes} seen)`);
|
|
3520
|
+
this.bytes = bytes;
|
|
3521
|
+
this.limit = limit;
|
|
3522
|
+
this.name = "ResponseTooLargeError";
|
|
3523
|
+
}
|
|
3524
|
+
bytes;
|
|
3525
|
+
limit;
|
|
3526
|
+
};
|
|
3527
|
+
function isStream(v) {
|
|
3528
|
+
return typeof v?.getReader === "function";
|
|
3529
|
+
}
|
|
3530
|
+
async function readJsonBounded(res, maxBytes) {
|
|
3531
|
+
const r = res;
|
|
3532
|
+
const declared = Number(r.headers?.get?.("content-length"));
|
|
3533
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
3534
|
+
if (isStream(r.body)) await r.body.cancel().catch(() => {
|
|
3535
|
+
});
|
|
3536
|
+
throw new ResponseTooLargeError(declared, maxBytes);
|
|
3537
|
+
}
|
|
3538
|
+
if (isStream(r.body)) {
|
|
3539
|
+
const reader = r.body.getReader();
|
|
3540
|
+
const chunks = [];
|
|
3541
|
+
let total = 0;
|
|
3542
|
+
for (; ; ) {
|
|
3543
|
+
const { done, value } = await reader.read();
|
|
3544
|
+
if (done) break;
|
|
3545
|
+
total += value.byteLength;
|
|
3546
|
+
if (total > maxBytes) {
|
|
3547
|
+
await reader.cancel().catch(() => {
|
|
3548
|
+
});
|
|
3549
|
+
throw new ResponseTooLargeError(total, maxBytes);
|
|
3550
|
+
}
|
|
3551
|
+
chunks.push(value);
|
|
3552
|
+
}
|
|
3553
|
+
return JSON.parse(new TextDecoder().decode(Buffer.concat(chunks)));
|
|
3554
|
+
}
|
|
3555
|
+
if (typeof r.text === "function") {
|
|
3556
|
+
const text = await r.text();
|
|
3557
|
+
const size = Buffer.byteLength(text);
|
|
3558
|
+
if (size > maxBytes) throw new ResponseTooLargeError(size, maxBytes);
|
|
3559
|
+
return JSON.parse(text);
|
|
3560
|
+
}
|
|
3561
|
+
if (typeof r.json === "function") return r.json();
|
|
3562
|
+
throw new TypeError("response has no readable body");
|
|
3563
|
+
}
|
|
3495
3564
|
var ESPN_SOCCER = "https://site.api.espn.com/apis/site/v2/sports/soccer";
|
|
3496
3565
|
var DEFAULT_COMPETITION = "fifa.world";
|
|
3497
3566
|
var DEFAULT_BASE = `${ESPN_SOCCER}/${DEFAULT_COMPETITION}`;
|
|
3498
|
-
var USER_AGENT = `claudinho/${"0.10.
|
|
3567
|
+
var USER_AGENT = `claudinho/${"0.10.1"} (+https://github.com/arturogarrido/claudinho)`;
|
|
3499
3568
|
var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
3569
|
+
var DEFAULT_COOLDOWN_MS = 5 * 6e4;
|
|
3570
|
+
var MAX_COOLDOWN_MS = 15 * 6e4;
|
|
3571
|
+
function retryAfterMs(header2, nowMs) {
|
|
3572
|
+
if (typeof header2 !== "string" || header2.trim() === "") return DEFAULT_COOLDOWN_MS;
|
|
3573
|
+
const h = header2.trim();
|
|
3574
|
+
let ms;
|
|
3575
|
+
if (/^\d+$/.test(h)) ms = Number(h) * 1e3;
|
|
3576
|
+
else {
|
|
3577
|
+
const at = Date.parse(h);
|
|
3578
|
+
if (Number.isFinite(at)) ms = at - nowMs;
|
|
3579
|
+
}
|
|
3580
|
+
if (ms === void 0 || !Number.isFinite(ms)) return DEFAULT_COOLDOWN_MS;
|
|
3581
|
+
return Math.min(Math.max(ms, 0), MAX_COOLDOWN_MS);
|
|
3582
|
+
}
|
|
3500
3583
|
function competitionBase(slug) {
|
|
3501
3584
|
return `${ESPN_SOCCER}/${slug}`;
|
|
3502
3585
|
}
|
|
@@ -3505,6 +3588,8 @@ var STANDINGS_SHARE_MS = 3e4;
|
|
|
3505
3588
|
var ProviderError = class extends Error {
|
|
3506
3589
|
kind;
|
|
3507
3590
|
status;
|
|
3591
|
+
/** For a throttle: how long the adapter will refuse to fetch (bounded). */
|
|
3592
|
+
retryAfterMs;
|
|
3508
3593
|
constructor(message, kind, status) {
|
|
3509
3594
|
super(message);
|
|
3510
3595
|
this.name = "ProviderError";
|
|
@@ -3528,6 +3613,7 @@ function usableProviderItems(kind, parsed, hasUsableRecord = parsed.items.length
|
|
|
3528
3613
|
var EspnAdapter = class {
|
|
3529
3614
|
constructor(opts = {}) {
|
|
3530
3615
|
this.opts = opts;
|
|
3616
|
+
this.clock = opts.now ?? (() => Date.now());
|
|
3531
3617
|
const expected = opts.expectedStandingsGroups ?? (opts.baseUrl === void 0 ? groups() : void 0);
|
|
3532
3618
|
this.expectedStandingsGroups = expected ? [...expected] : void 0;
|
|
3533
3619
|
this.standingsFallbackGroups = opts.baseUrl === void 0 && expected ? [...expected] : void 0;
|
|
@@ -3553,6 +3639,53 @@ var EspnAdapter = class {
|
|
|
3553
3639
|
* throttle (persist a backoff) from an ordinary blip.
|
|
3554
3640
|
*/
|
|
3555
3641
|
lastError;
|
|
3642
|
+
/**
|
|
3643
|
+
* A retained throttle (audit A12): after a 429/403 every call inside the
|
|
3644
|
+
* window throws the provider's last answer WITHOUT a request. A server-
|
|
3645
|
+
* lifetime MCP adapter is covered by this alone; the CLI pre-arms each
|
|
3646
|
+
* process from its persisted cache via `armCooldown`.
|
|
3647
|
+
*/
|
|
3648
|
+
cooldownUntilMs;
|
|
3649
|
+
cooldownError;
|
|
3650
|
+
cooldownListeners = /* @__PURE__ */ new Set();
|
|
3651
|
+
clock;
|
|
3652
|
+
/** Epoch ms until which requests are refused, when a cooldown is armed. */
|
|
3653
|
+
get cooldownUntil() {
|
|
3654
|
+
return this.cooldownUntilMs;
|
|
3655
|
+
}
|
|
3656
|
+
/**
|
|
3657
|
+
* Arm the cooldown from outside — a fresh CLI process reading the backoff
|
|
3658
|
+
* its refresher persisted. The retained error reads as a throttle so every
|
|
3659
|
+
* caller's `degraded` path and the refresher's persistence treat it as one.
|
|
3660
|
+
*/
|
|
3661
|
+
armCooldown(untilMs, reason) {
|
|
3662
|
+
const nowMs = this.clock();
|
|
3663
|
+
const error = reason ?? new ProviderError("ESPN request skipped: provider cooldown in effect", "http", 429);
|
|
3664
|
+
error.retryAfterMs = Math.max(0, untilMs - nowMs);
|
|
3665
|
+
this.arm(untilMs, error);
|
|
3666
|
+
}
|
|
3667
|
+
/**
|
|
3668
|
+
* Be told whenever the cooldown window is armed or EXTENDED — the way a
|
|
3669
|
+
* caller persists a throttle that arrives from a still-running request after
|
|
3670
|
+
* its own call already returned (review P2 on #128). Returns unsubscribe.
|
|
3671
|
+
*/
|
|
3672
|
+
onCooldown(listener) {
|
|
3673
|
+
this.cooldownListeners.add(listener);
|
|
3674
|
+
return () => {
|
|
3675
|
+
this.cooldownListeners.delete(listener);
|
|
3676
|
+
};
|
|
3677
|
+
}
|
|
3678
|
+
/**
|
|
3679
|
+
* The ONE place a window is set. Concurrent requests can each carry a
|
|
3680
|
+
* Retry-After; the LATEST expiry wins — a shorter one arriving second must
|
|
3681
|
+
* never shorten a longer active window (review P2 on #128).
|
|
3682
|
+
*/
|
|
3683
|
+
arm(untilMs, error) {
|
|
3684
|
+
if (this.cooldownUntilMs !== void 0 && untilMs <= this.cooldownUntilMs) return;
|
|
3685
|
+
this.cooldownUntilMs = untilMs;
|
|
3686
|
+
this.cooldownError = error;
|
|
3687
|
+
for (const listener of this.cooldownListeners) listener(untilMs);
|
|
3688
|
+
}
|
|
3556
3689
|
async fetchByDate(dateISO) {
|
|
3557
3690
|
return this.fetchScoreboard(toEspnDate(dateISO));
|
|
3558
3691
|
}
|
|
@@ -3640,6 +3773,11 @@ var EspnAdapter = class {
|
|
|
3640
3773
|
return usableProviderItems("scoreboard", parsed);
|
|
3641
3774
|
}
|
|
3642
3775
|
async get(url) {
|
|
3776
|
+
const nowMs = this.clock();
|
|
3777
|
+
if (this.cooldownError && this.cooldownUntilMs !== void 0 && nowMs < this.cooldownUntilMs) {
|
|
3778
|
+
this.lastError = this.cooldownError;
|
|
3779
|
+
throw this.cooldownError;
|
|
3780
|
+
}
|
|
3643
3781
|
const doFetch = this.opts.fetchImpl ?? fetch;
|
|
3644
3782
|
const controller = new AbortController();
|
|
3645
3783
|
const timer = setTimeout(
|
|
@@ -3661,19 +3799,24 @@ var EspnAdapter = class {
|
|
|
3661
3799
|
throw e?.name === "AbortError" ? new ProviderError(`ESPN request timed out: ${url}`, "timeout") : new ProviderError(`ESPN request failed: ${e?.message ?? e}`, "http");
|
|
3662
3800
|
}
|
|
3663
3801
|
if (!res.ok) {
|
|
3664
|
-
|
|
3802
|
+
const pe = new ProviderError(
|
|
3665
3803
|
`ESPN request failed: ${res.status} ${res.statusText}`,
|
|
3666
3804
|
"http",
|
|
3667
3805
|
res.status
|
|
3668
3806
|
);
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3807
|
+
if (pe.throttled) {
|
|
3808
|
+
const receiptMs = this.clock();
|
|
3809
|
+
pe.retryAfterMs = retryAfterMs(res.headers?.get?.("retry-after"), receiptMs);
|
|
3810
|
+
this.arm(receiptMs + pe.retryAfterMs, pe);
|
|
3811
|
+
}
|
|
3812
|
+
throw pe;
|
|
3673
3813
|
}
|
|
3674
3814
|
try {
|
|
3675
|
-
return await res
|
|
3815
|
+
return await readJsonBounded(res, MAX_RESPONSE_BYTES);
|
|
3676
3816
|
} catch (e) {
|
|
3817
|
+
if (e instanceof ResponseTooLargeError) {
|
|
3818
|
+
throw new ProviderError(`ESPN response too large: ${e.bytes} bytes`, "parse");
|
|
3819
|
+
}
|
|
3677
3820
|
throw new ProviderError(
|
|
3678
3821
|
`ESPN response unparseable: ${e?.message ?? e}`,
|
|
3679
3822
|
"parse"
|
|
@@ -3716,14 +3859,30 @@ function hasGroupStarted(group, tables) {
|
|
|
3716
3859
|
function matchesPerTeamInGroup(teamCount) {
|
|
3717
3860
|
return Math.max(0, teamCount - 1);
|
|
3718
3861
|
}
|
|
3719
|
-
function isGroupStandingsComplete(table) {
|
|
3720
|
-
|
|
3862
|
+
function isGroupStandingsComplete(table, expectedTeams) {
|
|
3863
|
+
if (!table || table.partial) return false;
|
|
3864
|
+
const n = table.rows.length;
|
|
3721
3865
|
if (n < 2) return false;
|
|
3722
|
-
|
|
3866
|
+
if (expectedTeams !== void 0 && n < expectedTeams) return false;
|
|
3867
|
+
const required = matchesPerTeamInGroup(Math.max(n, expectedTeams ?? n));
|
|
3723
3868
|
return table.rows.every((r) => r.played >= required);
|
|
3724
3869
|
}
|
|
3870
|
+
function bundledGroupSize(group) {
|
|
3871
|
+
const codes = /* @__PURE__ */ new Set();
|
|
3872
|
+
for (const m of fixturesByGroup(group)) {
|
|
3873
|
+
codes.add(m.home.code);
|
|
3874
|
+
codes.add(m.away.code);
|
|
3875
|
+
}
|
|
3876
|
+
return codes.size > 0 ? codes.size : void 0;
|
|
3877
|
+
}
|
|
3725
3878
|
function isGroupComplete(group, tables) {
|
|
3726
|
-
return isGroupStandingsComplete(
|
|
3879
|
+
return isGroupStandingsComplete(
|
|
3880
|
+
tables.find((t2) => t2.group === group),
|
|
3881
|
+
bundledGroupSize(group)
|
|
3882
|
+
);
|
|
3883
|
+
}
|
|
3884
|
+
function isGroupPartial(group, tables) {
|
|
3885
|
+
return tables.find((t2) => t2.group === group)?.partial !== void 0;
|
|
3727
3886
|
}
|
|
3728
3887
|
function resolveWinner(match) {
|
|
3729
3888
|
if (!isFinished(match.status)) return void 0;
|
|
@@ -3757,8 +3916,8 @@ function resolveLoser(match) {
|
|
|
3757
3916
|
function participant(team, status) {
|
|
3758
3917
|
return { label: team.name, flag: team.flag, code: team.code, status };
|
|
3759
3918
|
}
|
|
3760
|
-
function tbd(
|
|
3761
|
-
return { label, flag: "\u{1F3F3}\uFE0F", status: "tbd" };
|
|
3919
|
+
function tbd(label2) {
|
|
3920
|
+
return { label: label2, flag: "\u{1F3F3}\uFE0F", status: "tbd" };
|
|
3762
3921
|
}
|
|
3763
3922
|
function confirmedLiveParticipant(team) {
|
|
3764
3923
|
if (!team || team.flag === "\u{1F3F3}\uFE0F") return void 0;
|
|
@@ -3778,15 +3937,15 @@ function resolveSlot(ref, ctx, liveTeam, fixtureInMergedSet = false) {
|
|
|
3778
3937
|
return tbd(ref.label);
|
|
3779
3938
|
case "group": {
|
|
3780
3939
|
if (liveParticipant) return liveParticipant;
|
|
3781
|
-
if (!ctx.standingsDegraded && hasGroupStarted(ref.group, ctx.tables)) {
|
|
3940
|
+
if (!ctx.standingsDegraded && !isGroupPartial(ref.group, ctx.tables) && hasGroupStarted(ref.group, ctx.tables)) {
|
|
3782
3941
|
const team = teamFromStandings(ref.group, ref.position, ctx.tables);
|
|
3783
3942
|
if (team) {
|
|
3784
3943
|
const status = isGroupComplete(ref.group, ctx.tables) ? "confirmed" : "projected";
|
|
3785
3944
|
return participant(team, status);
|
|
3786
3945
|
}
|
|
3787
3946
|
}
|
|
3788
|
-
const
|
|
3789
|
-
return tbd(
|
|
3947
|
+
const label2 = ref.position === 1 ? t(ctx.lang, "bracket.slot.groupWinner", { group: ref.group }) : t(ctx.lang, "bracket.slot.groupSecond", { group: ref.group });
|
|
3948
|
+
return tbd(label2);
|
|
3790
3949
|
}
|
|
3791
3950
|
case "third":
|
|
3792
3951
|
if (liveParticipant) return liveParticipant;
|
|
@@ -4412,13 +4571,17 @@ function resolveCompetition(explicit) {
|
|
|
4412
4571
|
}
|
|
4413
4572
|
return DEFAULT_COMPETITION;
|
|
4414
4573
|
}
|
|
4574
|
+
var BUNDLE_COMPETITION = DEFAULT_COMPETITION;
|
|
4575
|
+
function bundleApplies(competition = resolveCompetition()) {
|
|
4576
|
+
return competition === BUNDLE_COMPETITION;
|
|
4577
|
+
}
|
|
4415
4578
|
var KNOWN_SOURCES = ["espn"];
|
|
4416
4579
|
function makeAdapter(source = "espn", opts = {}) {
|
|
4417
4580
|
switch (source) {
|
|
4418
4581
|
case "espn": {
|
|
4419
4582
|
const competition = resolveCompetition();
|
|
4420
4583
|
const baseUrl = competition === DEFAULT_COMPETITION ? void 0 : competitionBase(competition);
|
|
4421
|
-
return new EspnAdapter({ baseUrl, enrichGroups: opts.enrichGroups });
|
|
4584
|
+
return new EspnAdapter({ baseUrl, enrichGroups: opts.enrichGroups, now: opts.now });
|
|
4422
4585
|
}
|
|
4423
4586
|
default:
|
|
4424
4587
|
throw new Error(
|
|
@@ -4436,7 +4599,7 @@ function liveSourceLabel(source) {
|
|
|
4436
4599
|
return known[source] ?? source.charAt(0).toUpperCase() + source.slice(1);
|
|
4437
4600
|
}
|
|
4438
4601
|
async function getMatchesForDate(adapter, dateISO) {
|
|
4439
|
-
const base = allFixtures();
|
|
4602
|
+
const base = bundleApplies() ? allFixtures() : [];
|
|
4440
4603
|
const day = dateISO.slice(0, 10);
|
|
4441
4604
|
try {
|
|
4442
4605
|
const live = adapter.fetchWindow ? await adapter.fetchWindow(shiftUtcDate(day, -1), shiftUtcDate(day, 1)) : await adapter.fetchByDate(day);
|
|
@@ -4481,21 +4644,27 @@ function knockoutWindow() {
|
|
|
4481
4644
|
return knockoutWindowMemo;
|
|
4482
4645
|
}
|
|
4483
4646
|
async function getBracket(adapter, opts = {}) {
|
|
4647
|
+
if (!bundleApplies()) {
|
|
4648
|
+
const view2 = { stages: [], degraded: false, standingsDegraded: false, unsupported: true };
|
|
4649
|
+
return { view: view2, degraded: false, standingsDegraded: false, unsupported: true };
|
|
4650
|
+
}
|
|
4484
4651
|
const topology = loadBracketTopology();
|
|
4485
4652
|
const base = allFixtures().filter((m) => m.stage !== "GROUP" && m.stage !== "FRIENDLY");
|
|
4486
4653
|
let matches = base;
|
|
4487
4654
|
let liveDegraded = true;
|
|
4488
4655
|
let source;
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4656
|
+
const win = knockoutWindow();
|
|
4657
|
+
if (adapter.fetchWindow && win) {
|
|
4658
|
+
try {
|
|
4659
|
+
const live = await adapter.fetchWindow(win.start, win.end);
|
|
4660
|
+
matches = mergeLive(base, live);
|
|
4661
|
+
liveDegraded = false;
|
|
4662
|
+
source = adapter.name;
|
|
4663
|
+
} catch {
|
|
4664
|
+
}
|
|
4496
4665
|
}
|
|
4497
4666
|
const standings = await getStandings(adapter);
|
|
4498
|
-
if (!source && !standings.degraded && standings.source) {
|
|
4667
|
+
if (!source && !standings.degraded && standings.source && standings.tables.length > 0) {
|
|
4499
4668
|
source = standings.source;
|
|
4500
4669
|
}
|
|
4501
4670
|
const view = buildBracketView(
|
|
@@ -4517,18 +4686,18 @@ async function getBracket(adapter, opts = {}) {
|
|
|
4517
4686
|
}
|
|
4518
4687
|
var EXTRA_TIME_SLACK_MS = 60 * 6e4;
|
|
4519
4688
|
async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Date()) {
|
|
4689
|
+
if (!bundleApplies()) return { match: void 0, degraded: false, unsupported: true };
|
|
4520
4690
|
const nowMs = now.getTime();
|
|
4521
4691
|
let fixtures = allFixtures();
|
|
4522
4692
|
let overlayFailed = false;
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
fixtures = mergeLive(
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
);
|
|
4693
|
+
const win = knockoutWindow();
|
|
4694
|
+
if (adapter.fetchWindow && win) {
|
|
4695
|
+
try {
|
|
4696
|
+
fixtures = mergeLive(fixtures, await adapter.fetchWindow(win.start, win.end));
|
|
4697
|
+
} catch {
|
|
4698
|
+
overlayFailed = true;
|
|
4530
4699
|
}
|
|
4531
|
-
}
|
|
4700
|
+
} else {
|
|
4532
4701
|
overlayFailed = true;
|
|
4533
4702
|
}
|
|
4534
4703
|
const candidate = fixturesByTeam(code, fixtures).find((m) => {
|
|
@@ -4544,23 +4713,27 @@ async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Dat
|
|
|
4544
4713
|
return { match: next, degraded: overlayFailed };
|
|
4545
4714
|
}
|
|
4546
4715
|
async function getNextFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Date()) {
|
|
4716
|
+
if (!bundleApplies()) return { fixture: void 0, degraded: false, unsupported: true };
|
|
4547
4717
|
const base = allFixtures();
|
|
4548
4718
|
let matches = base;
|
|
4549
4719
|
let degraded = true;
|
|
4550
4720
|
let liveById;
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4721
|
+
const win = knockoutWindow();
|
|
4722
|
+
if (adapter.fetchWindow && win) {
|
|
4723
|
+
try {
|
|
4724
|
+
const live = await adapter.fetchWindow(win.start, win.end);
|
|
4725
|
+
matches = mergeLive(base, live);
|
|
4726
|
+
degraded = false;
|
|
4727
|
+
liveById = new Set(live.map((m) => m.id));
|
|
4728
|
+
} catch {
|
|
4729
|
+
}
|
|
4558
4730
|
}
|
|
4559
4731
|
const fixture = nextFixtureForTeam(code, { from: now, fixtures: matches });
|
|
4560
4732
|
const source = fixture && liveById?.has(fixture.id) ? adapter.name : void 0;
|
|
4561
4733
|
return { fixture, degraded, source };
|
|
4562
4734
|
}
|
|
4563
4735
|
async function getKnockoutFixtures(adapter, now = /* @__PURE__ */ new Date()) {
|
|
4736
|
+
if (!bundleApplies()) return { fixtures: [], degraded: true, unsupported: true };
|
|
4564
4737
|
const win = knockoutWindow();
|
|
4565
4738
|
if (!adapter.fetchWindow || !win) return { fixtures: [], degraded: true };
|
|
4566
4739
|
let live;
|
|
@@ -4569,13 +4742,13 @@ async function getKnockoutFixtures(adapter, now = /* @__PURE__ */ new Date()) {
|
|
|
4569
4742
|
} catch {
|
|
4570
4743
|
return { fixtures: [], degraded: true };
|
|
4571
4744
|
}
|
|
4572
|
-
const nowMs = now.getTime();
|
|
4573
4745
|
const fixtures = live.filter(
|
|
4574
|
-
(m) => m.stage !== "GROUP" && m.stage !== "FRIENDLY" &&
|
|
4746
|
+
(m) => m.stage !== "GROUP" && m.stage !== "FRIENDLY" && isUpcoming(m, now) && isResolvedNation(m.home) && isResolvedNation(m.away)
|
|
4575
4747
|
).sort(byKickoff);
|
|
4576
4748
|
return { fixtures, degraded: false };
|
|
4577
4749
|
}
|
|
4578
4750
|
async function getMatchById(adapter, id) {
|
|
4751
|
+
if (!bundleApplies()) return { match: void 0, degraded: false, unsupported: true };
|
|
4579
4752
|
const base = allFixtures().find((m) => m.id === id);
|
|
4580
4753
|
if (!base) return { match: void 0, degraded: false };
|
|
4581
4754
|
const day = base.kickoff.slice(0, 10);
|
|
@@ -5016,11 +5189,15 @@ var PolymarketProvider = class {
|
|
|
5016
5189
|
if (!res.ok) {
|
|
5017
5190
|
throw new Error(`Polymarket request failed: ${res.status} ${res.statusText}`);
|
|
5018
5191
|
}
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5192
|
+
let data;
|
|
5193
|
+
try {
|
|
5194
|
+
data = await readJsonBounded(res, MAX_RESPONSE_BYTES);
|
|
5195
|
+
} catch (e) {
|
|
5196
|
+
if (e instanceof ResponseTooLargeError) {
|
|
5197
|
+
throw new Error(`Polymarket response too large: ${e.bytes} bytes`);
|
|
5198
|
+
}
|
|
5199
|
+
throw e;
|
|
5022
5200
|
}
|
|
5023
|
-
const data = await res.json();
|
|
5024
5201
|
if (Array.isArray(data) && data.length > 1) {
|
|
5025
5202
|
return ambiguous("slug returned more than one event");
|
|
5026
5203
|
}
|
|
@@ -5100,7 +5277,7 @@ var PolymarketProvider = class {
|
|
|
5100
5277
|
const outcomes = [];
|
|
5101
5278
|
let asOf = canonicalTimestamp(event.updatedAt);
|
|
5102
5279
|
let liquidity;
|
|
5103
|
-
for (const [kind, market, teamCode2,
|
|
5280
|
+
for (const [kind, market, teamCode2, label2] of legs) {
|
|
5104
5281
|
if (!market) continue;
|
|
5105
5282
|
if (typeof market.closed !== "boolean" || typeof market.active !== "boolean") {
|
|
5106
5283
|
return malformed("market active/closed is not a boolean");
|
|
@@ -5113,7 +5290,7 @@ var PolymarketProvider = class {
|
|
|
5113
5290
|
}
|
|
5114
5291
|
const yes = yesPrice(market);
|
|
5115
5292
|
if (yes == null) return malformed("market is not a readable Yes/No binary");
|
|
5116
|
-
outcomes.push({ kind, teamCode: teamCode2, label, probability: yes });
|
|
5293
|
+
outcomes.push({ kind, teamCode: teamCode2, label: label2, probability: yes });
|
|
5117
5294
|
const marketAsOf = canonicalTimestamp(market.updatedAt);
|
|
5118
5295
|
if (!marketAsOf) {
|
|
5119
5296
|
return malformed("market updatedAt missing or unparseable");
|
|
@@ -5427,10 +5604,13 @@ function formatShareTable(input, options = {}) {
|
|
|
5427
5604
|
input.emptyNote ?? (input.degraded ? "Live standings unavailable." : "No standings available.")
|
|
5428
5605
|
);
|
|
5429
5606
|
} else {
|
|
5430
|
-
for (const { group, rows } of input.tables) {
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5607
|
+
for (const { group, rows, partial } of input.tables) {
|
|
5608
|
+
const lines = [`Group ${group} \xB7 standings`, "", ...rows.map((r, i) => tableRow(r, r.rank ?? i + 1))];
|
|
5609
|
+
if (partial) {
|
|
5610
|
+
const n = partial.omitted;
|
|
5611
|
+
lines.push("", `(partial table \u2014 ${n} row${n === 1 ? "" : "s"} unreadable; positions are the provider's ranks)`);
|
|
5612
|
+
}
|
|
5613
|
+
blocks.push(lines.join("\n"));
|
|
5434
5614
|
}
|
|
5435
5615
|
if (input.degraded) {
|
|
5436
5616
|
blocks.push("(Live standings unavailable \u2014 group roster, not live results.)");
|
|
@@ -5614,11 +5794,56 @@ function resolveConfig(opts) {
|
|
|
5614
5794
|
|
|
5615
5795
|
// src/cursorPayload.ts
|
|
5616
5796
|
import { readFileSync } from "fs";
|
|
5797
|
+
var MAX_CURSOR_PAYLOAD_BYTES = 64 * 1024;
|
|
5798
|
+
var MAX_CURSOR_LABEL_COLUMNS = 40;
|
|
5799
|
+
function label(v) {
|
|
5800
|
+
const s = humanLabel(v, MAX_CURSOR_LABEL_COLUMNS);
|
|
5801
|
+
return s === "" ? void 0 : s;
|
|
5802
|
+
}
|
|
5803
|
+
function finite(v) {
|
|
5804
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
5805
|
+
}
|
|
5806
|
+
function record(v) {
|
|
5807
|
+
return v !== null && typeof v === "object" && !Array.isArray(v) ? v : void 0;
|
|
5808
|
+
}
|
|
5809
|
+
function validateCursorPayload(parsed) {
|
|
5810
|
+
const p = record(parsed);
|
|
5811
|
+
if (!p) return void 0;
|
|
5812
|
+
const out2 = {};
|
|
5813
|
+
const model = record(p.model);
|
|
5814
|
+
if (model) {
|
|
5815
|
+
const display_name = label(model.display_name);
|
|
5816
|
+
const param_summary = label(model.param_summary);
|
|
5817
|
+
if (display_name || param_summary) {
|
|
5818
|
+
out2.model = {
|
|
5819
|
+
...display_name ? { display_name } : {},
|
|
5820
|
+
...param_summary ? { param_summary } : {}
|
|
5821
|
+
};
|
|
5822
|
+
}
|
|
5823
|
+
}
|
|
5824
|
+
const cw = record(p.context_window);
|
|
5825
|
+
if (cw) {
|
|
5826
|
+
if (cw.used_percentage === null) out2.context_window = { used_percentage: null };
|
|
5827
|
+
else {
|
|
5828
|
+
const pct2 = finite(cw.used_percentage);
|
|
5829
|
+
if (pct2 !== void 0) out2.context_window = { used_percentage: pct2 };
|
|
5830
|
+
}
|
|
5831
|
+
}
|
|
5832
|
+
const wt = record(p.worktree);
|
|
5833
|
+
const name = wt ? label(wt.name) : void 0;
|
|
5834
|
+
if (name) out2.worktree = { name };
|
|
5835
|
+
const vim = record(p.vim);
|
|
5836
|
+
const mode = vim ? label(vim.mode) : void 0;
|
|
5837
|
+
if (mode) out2.vim = { mode };
|
|
5838
|
+
const width = finite(p.render_width_chars);
|
|
5839
|
+
if (width !== void 0 && width > 0) out2.render_width_chars = Math.floor(width);
|
|
5840
|
+
return out2;
|
|
5841
|
+
}
|
|
5617
5842
|
function parseCursorPayload(raw) {
|
|
5618
5843
|
try {
|
|
5619
5844
|
const trimmed = raw.trim();
|
|
5620
5845
|
if (!trimmed) return void 0;
|
|
5621
|
-
return JSON.parse(trimmed);
|
|
5846
|
+
return validateCursorPayload(JSON.parse(trimmed));
|
|
5622
5847
|
} catch {
|
|
5623
5848
|
return void 0;
|
|
5624
5849
|
}
|
|
@@ -5631,19 +5856,27 @@ function readCursorPayload() {
|
|
|
5631
5856
|
return void 0;
|
|
5632
5857
|
}
|
|
5633
5858
|
}
|
|
5634
|
-
function readCursorPayloadBounded(timeoutMs = 100, stdin = process.stdin) {
|
|
5859
|
+
function readCursorPayloadBounded(timeoutMs = 100, stdin = process.stdin, maxBytes = MAX_CURSOR_PAYLOAD_BYTES) {
|
|
5635
5860
|
if (stdin.isTTY) return Promise.resolve(void 0);
|
|
5636
5861
|
return new Promise((resolve) => {
|
|
5637
5862
|
const chunks = [];
|
|
5863
|
+
let total = 0;
|
|
5864
|
+
let overflow = false;
|
|
5638
5865
|
const done = () => {
|
|
5639
5866
|
clearTimeout(timer);
|
|
5640
5867
|
stdin.off("data", onData);
|
|
5641
5868
|
stdin.off("end", done);
|
|
5642
5869
|
stdin.off("error", done);
|
|
5643
5870
|
stdin.pause();
|
|
5644
|
-
resolve(parseCursorPayload(Buffer.concat(chunks).toString("utf8")));
|
|
5871
|
+
resolve(overflow ? void 0 : parseCursorPayload(Buffer.concat(chunks).toString("utf8")));
|
|
5645
5872
|
};
|
|
5646
5873
|
const onData = (c) => {
|
|
5874
|
+
total += c.length;
|
|
5875
|
+
if (total > maxBytes) {
|
|
5876
|
+
overflow = true;
|
|
5877
|
+
done();
|
|
5878
|
+
return;
|
|
5879
|
+
}
|
|
5647
5880
|
chunks.push(c);
|
|
5648
5881
|
};
|
|
5649
5882
|
const timer = setTimeout(done, timeoutMs);
|
|
@@ -5666,18 +5899,19 @@ function cursorMetaEnabled(payload) {
|
|
|
5666
5899
|
}
|
|
5667
5900
|
function renderCursorMetaLine(payload) {
|
|
5668
5901
|
const parts = [];
|
|
5669
|
-
const model = payload.model?.display_name;
|
|
5902
|
+
const model = label(payload.model?.display_name);
|
|
5670
5903
|
if (model) {
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
parts.push(label);
|
|
5904
|
+
const summary = label(payload.model?.param_summary);
|
|
5905
|
+
parts.push(summary ? `${model} ${summary}` : model);
|
|
5674
5906
|
}
|
|
5675
5907
|
const pct2 = payload.context_window?.used_percentage;
|
|
5676
5908
|
if (typeof pct2 === "number" && Number.isFinite(pct2)) {
|
|
5677
5909
|
parts.push(`ctx ${Math.floor(pct2)}%`);
|
|
5678
5910
|
}
|
|
5679
|
-
|
|
5680
|
-
if (
|
|
5911
|
+
const wt = label(payload.worktree?.name);
|
|
5912
|
+
if (wt) parts.push(`wt ${wt}`);
|
|
5913
|
+
const vim = label(payload.vim?.mode);
|
|
5914
|
+
if (vim) parts.push(vim);
|
|
5681
5915
|
if (parts.length === 0) return void 0;
|
|
5682
5916
|
return `\x1B[90m${parts.join(" ")}\x1B[0m`;
|
|
5683
5917
|
}
|
|
@@ -5711,6 +5945,8 @@ var EN2 = {
|
|
|
5711
5945
|
"table.degraded": "Live standings unavailable \u2014 showing the group roster.",
|
|
5712
5946
|
"table.unavailable": "Live standings unavailable.",
|
|
5713
5947
|
"table.empty": "No standings available.",
|
|
5948
|
+
"table.partial": "Partial table \u2014 {n} rows could not be read.",
|
|
5949
|
+
"competition.unsupported": "Not available for this competition yet.",
|
|
5714
5950
|
"match.none": "No match found with id {id}.",
|
|
5715
5951
|
"status.live": "LIVE",
|
|
5716
5952
|
"status.ht": "HT",
|
|
@@ -5752,6 +5988,8 @@ var ES2 = {
|
|
|
5752
5988
|
"table.degraded": "Tabla en vivo no disponible \u2014 mostrando la lista del grupo.",
|
|
5753
5989
|
"table.unavailable": "Tabla en vivo no disponible.",
|
|
5754
5990
|
"table.empty": "No hay clasificaci\xF3n disponible.",
|
|
5991
|
+
"table.partial": "Tabla parcial \u2014 no se pudieron leer {n} filas.",
|
|
5992
|
+
"competition.unsupported": "A\xFAn no disponible para esta competici\xF3n.",
|
|
5755
5993
|
"match.none": "No se encontr\xF3 partido con id {id}.",
|
|
5756
5994
|
"status.live": "EN VIVO",
|
|
5757
5995
|
"status.ht": "DESC",
|
|
@@ -5793,6 +6031,8 @@ var PT2 = {
|
|
|
5793
6031
|
"table.degraded": "Classifica\xE7\xE3o ao vivo indispon\xEDvel \u2014 mostrando os times do grupo.",
|
|
5794
6032
|
"table.unavailable": "Classifica\xE7\xE3o ao vivo indispon\xEDvel.",
|
|
5795
6033
|
"table.empty": "Classifica\xE7\xE3o indispon\xEDvel.",
|
|
6034
|
+
"table.partial": "Tabela parcial \u2014 {n} linhas n\xE3o puderam ser lidas.",
|
|
6035
|
+
"competition.unsupported": "Ainda n\xE3o dispon\xEDvel para esta competi\xE7\xE3o.",
|
|
5796
6036
|
"match.none": "Nenhum jogo encontrado com id {id}.",
|
|
5797
6037
|
"status.live": "AO VIVO",
|
|
5798
6038
|
"status.ht": "INT",
|
|
@@ -5834,6 +6074,8 @@ var FR2 = {
|
|
|
5834
6074
|
"table.degraded": "Classement en direct indisponible \u2014 affichage de la composition du groupe.",
|
|
5835
6075
|
"table.unavailable": "Classement en direct indisponible.",
|
|
5836
6076
|
"table.empty": "Aucun classement disponible.",
|
|
6077
|
+
"table.partial": "Classement partiel \u2014 {n} lignes n'ont pas pu \xEAtre lues.",
|
|
6078
|
+
"competition.unsupported": "Pas encore disponible pour cette comp\xE9tition.",
|
|
5837
6079
|
"match.none": "Aucun match trouv\xE9 avec id {id}.",
|
|
5838
6080
|
"status.live": "DIRECT",
|
|
5839
6081
|
"status.ht": "MT",
|
|
@@ -5943,7 +6185,7 @@ function dataSource(source, lang, c) {
|
|
|
5943
6185
|
}
|
|
5944
6186
|
|
|
5945
6187
|
// src/marketCache.ts
|
|
5946
|
-
import { readFileSync as readFileSync3, statSync as
|
|
6188
|
+
import { readFileSync as readFileSync3, statSync as statSync3 } from "fs";
|
|
5947
6189
|
import { join as join3 } from "path";
|
|
5948
6190
|
|
|
5949
6191
|
// src/cache.ts
|
|
@@ -5952,30 +6194,68 @@ import {
|
|
|
5952
6194
|
mkdirSync as mkdirSync2,
|
|
5953
6195
|
openSync as openSync2,
|
|
5954
6196
|
readFileSync as readFileSync2,
|
|
5955
|
-
rmSync,
|
|
5956
|
-
statSync,
|
|
6197
|
+
rmSync as rmSync2,
|
|
6198
|
+
statSync as statSync2,
|
|
5957
6199
|
writeSync as writeSync2
|
|
5958
6200
|
} from "fs";
|
|
5959
6201
|
import { join as join2 } from "path";
|
|
6202
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
5960
6203
|
|
|
5961
6204
|
// src/paths.ts
|
|
5962
|
-
import {
|
|
6205
|
+
import { randomBytes } from "crypto";
|
|
6206
|
+
import {
|
|
6207
|
+
closeSync,
|
|
6208
|
+
fchmodSync,
|
|
6209
|
+
lstatSync,
|
|
6210
|
+
mkdirSync,
|
|
6211
|
+
openSync,
|
|
6212
|
+
realpathSync,
|
|
6213
|
+
renameSync,
|
|
6214
|
+
rmSync,
|
|
6215
|
+
statSync,
|
|
6216
|
+
writeSync
|
|
6217
|
+
} from "fs";
|
|
5963
6218
|
import { homedir } from "os";
|
|
5964
6219
|
import { dirname, join } from "path";
|
|
5965
6220
|
function cacheDir() {
|
|
5966
6221
|
const base = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
|
|
5967
6222
|
return join(base, "claudinho");
|
|
5968
6223
|
}
|
|
5969
|
-
function writeFileAtomic(path, data) {
|
|
6224
|
+
function writeFileAtomic(path, data, opts = {}) {
|
|
5970
6225
|
mkdirSync(dirname(path), { recursive: true });
|
|
5971
|
-
|
|
5972
|
-
|
|
6226
|
+
let target = path;
|
|
6227
|
+
let existingMode;
|
|
6228
|
+
let entry;
|
|
6229
|
+
try {
|
|
6230
|
+
entry = lstatSync(path);
|
|
6231
|
+
} catch {
|
|
6232
|
+
entry = void 0;
|
|
6233
|
+
}
|
|
6234
|
+
if (entry?.isSymbolicLink()) {
|
|
6235
|
+
if (opts.followSymlinks) {
|
|
6236
|
+
target = realpathSync(path);
|
|
6237
|
+
existingMode = statSync(target).mode & 511;
|
|
6238
|
+
}
|
|
6239
|
+
} else if (entry) {
|
|
6240
|
+
existingMode = entry.mode & 511;
|
|
6241
|
+
}
|
|
6242
|
+
const tmp = `${target}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
6243
|
+
const fd = openSync(tmp, "wx", existingMode ?? opts.mode ?? 438);
|
|
5973
6244
|
try {
|
|
6245
|
+
if (existingMode !== void 0) fchmodSync(fd, existingMode);
|
|
5974
6246
|
writeSync(fd, data);
|
|
5975
|
-
}
|
|
6247
|
+
} catch (e) {
|
|
5976
6248
|
closeSync(fd);
|
|
6249
|
+
rmSync(tmp, { force: true });
|
|
6250
|
+
throw e;
|
|
6251
|
+
}
|
|
6252
|
+
closeSync(fd);
|
|
6253
|
+
try {
|
|
6254
|
+
renameSync(tmp, target);
|
|
6255
|
+
} catch (e) {
|
|
6256
|
+
rmSync(tmp, { force: true });
|
|
6257
|
+
throw e;
|
|
5977
6258
|
}
|
|
5978
|
-
renameSync(tmp, path);
|
|
5979
6259
|
}
|
|
5980
6260
|
|
|
5981
6261
|
// src/cache.ts
|
|
@@ -6029,7 +6309,7 @@ function isCacheState(value) {
|
|
|
6029
6309
|
function readState(source = "espn", competition = DEFAULT_COMPETITION) {
|
|
6030
6310
|
try {
|
|
6031
6311
|
const path = cachePath(source, competition);
|
|
6032
|
-
const info =
|
|
6312
|
+
const info = statSync2(path);
|
|
6033
6313
|
if (!info.isFile() || info.size > MAX_STATE_BYTES) return void 0;
|
|
6034
6314
|
const bytes = readFileSync2(path);
|
|
6035
6315
|
if (bytes.byteLength > MAX_STATE_BYTES) return void 0;
|
|
@@ -6083,7 +6363,7 @@ function lockAgeMs(now = Date.now()) {
|
|
|
6083
6363
|
return Infinity;
|
|
6084
6364
|
}
|
|
6085
6365
|
try {
|
|
6086
|
-
return stampAgeMs(new Date(
|
|
6366
|
+
return stampAgeMs(new Date(statSync2(lp).mtimeMs).toISOString(), now);
|
|
6087
6367
|
} catch {
|
|
6088
6368
|
return Infinity;
|
|
6089
6369
|
}
|
|
@@ -6091,44 +6371,62 @@ function lockAgeMs(now = Date.now()) {
|
|
|
6091
6371
|
function isLockFresh(now = Date.now()) {
|
|
6092
6372
|
return lockAgeMs(now) < LOCK_STALE_MS;
|
|
6093
6373
|
}
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6374
|
+
var heldToken;
|
|
6375
|
+
function readLockToken() {
|
|
6376
|
+
try {
|
|
6377
|
+
return readFileSync2(lockPath(), "utf8").trim();
|
|
6378
|
+
} catch {
|
|
6379
|
+
return void 0;
|
|
6380
|
+
}
|
|
6381
|
+
}
|
|
6382
|
+
function writeExclusive(lp, token) {
|
|
6097
6383
|
try {
|
|
6098
6384
|
const fd = openSync2(lp, "wx");
|
|
6099
6385
|
try {
|
|
6100
|
-
writeSync2(fd,
|
|
6386
|
+
writeSync2(fd, token);
|
|
6101
6387
|
} finally {
|
|
6102
6388
|
closeSync2(fd);
|
|
6103
6389
|
}
|
|
6104
6390
|
return true;
|
|
6105
6391
|
} catch {
|
|
6106
|
-
if (lockAgeMs(now) > LOCK_STALE_MS) {
|
|
6107
|
-
try {
|
|
6108
|
-
rmSync(lp, { force: true });
|
|
6109
|
-
} catch {
|
|
6110
|
-
return false;
|
|
6111
|
-
}
|
|
6112
|
-
try {
|
|
6113
|
-
const fd = openSync2(lp, "wx");
|
|
6114
|
-
try {
|
|
6115
|
-
writeSync2(fd, `${process.pid} ${now}`);
|
|
6116
|
-
} finally {
|
|
6117
|
-
closeSync2(fd);
|
|
6118
|
-
}
|
|
6119
|
-
return true;
|
|
6120
|
-
} catch {
|
|
6121
|
-
return false;
|
|
6122
|
-
}
|
|
6123
|
-
}
|
|
6124
6392
|
return false;
|
|
6125
6393
|
}
|
|
6126
6394
|
}
|
|
6127
|
-
function
|
|
6395
|
+
function claimLock(now = Date.now()) {
|
|
6396
|
+
mkdirSync2(cacheDir(), { recursive: true });
|
|
6397
|
+
const lp = lockPath();
|
|
6398
|
+
const token = `${process.pid} ${now} ${randomBytes2(6).toString("hex")}`;
|
|
6399
|
+
if (writeExclusive(lp, token)) return token;
|
|
6400
|
+
if (lockAgeMs(now) > LOCK_STALE_MS) {
|
|
6401
|
+
try {
|
|
6402
|
+
rmSync2(lp, { force: true });
|
|
6403
|
+
} catch {
|
|
6404
|
+
return void 0;
|
|
6405
|
+
}
|
|
6406
|
+
return writeExclusive(lp, token) ? token : void 0;
|
|
6407
|
+
}
|
|
6408
|
+
return void 0;
|
|
6409
|
+
}
|
|
6410
|
+
function holdsLock(token = heldToken) {
|
|
6411
|
+
return token !== void 0 && readLockToken() === token;
|
|
6412
|
+
}
|
|
6413
|
+
function acquireLock(now = Date.now()) {
|
|
6414
|
+
const token = claimLock(now);
|
|
6415
|
+
if (token) heldToken = token;
|
|
6416
|
+
return token !== void 0;
|
|
6417
|
+
}
|
|
6418
|
+
function releaseLock(token = heldToken) {
|
|
6419
|
+
if (!holdsLock(token)) return;
|
|
6128
6420
|
try {
|
|
6129
|
-
|
|
6421
|
+
rmSync2(lockPath(), { force: true });
|
|
6130
6422
|
} catch {
|
|
6131
6423
|
}
|
|
6424
|
+
if (token === heldToken) heldToken = void 0;
|
|
6425
|
+
}
|
|
6426
|
+
function publishState(state, token = heldToken) {
|
|
6427
|
+
if (!holdsLock(token)) return false;
|
|
6428
|
+
writeState(state);
|
|
6429
|
+
return true;
|
|
6132
6430
|
}
|
|
6133
6431
|
|
|
6134
6432
|
// src/marketCache.ts
|
|
@@ -6142,7 +6440,7 @@ function cachePath2() {
|
|
|
6142
6440
|
function readFile() {
|
|
6143
6441
|
try {
|
|
6144
6442
|
const path = cachePath2();
|
|
6145
|
-
const info =
|
|
6443
|
+
const info = statSync3(path);
|
|
6146
6444
|
if (!info.isFile() || info.size > MAX_MARKET_CACHE_BYTES) return void 0;
|
|
6147
6445
|
const bytes = readFileSync3(path);
|
|
6148
6446
|
if (bytes.byteLength > MAX_MARKET_CACHE_BYTES) return void 0;
|
|
@@ -6316,7 +6614,7 @@ function isMatchShaped(m) {
|
|
|
6316
6614
|
return !!x && typeof x === "object" && typeof x.id === "string" && typeof x.kickoff === "string" && !!x.home?.code && !!x.away?.code;
|
|
6317
6615
|
}
|
|
6318
6616
|
function nextOverall(now, fixtures = allFixtures()) {
|
|
6319
|
-
return [...fixtures].sort(byKickoff).find((m) =>
|
|
6617
|
+
return [...fixtures].sort(byKickoff).find((m) => isUpcoming(m, new Date(now)) && isResolvedFixture(m));
|
|
6320
6618
|
}
|
|
6321
6619
|
function teamTok(t2, flags) {
|
|
6322
6620
|
return flags ? t2.flag : t2.code;
|
|
@@ -6420,7 +6718,8 @@ function renderPromptLine(state, opts = {}) {
|
|
|
6420
6718
|
const live = liveList.items;
|
|
6421
6719
|
const cachedFixtureList = sealFixtures(state?.fixtures);
|
|
6422
6720
|
const cachedFixtures = [...cachedFixtureList.items];
|
|
6423
|
-
const
|
|
6721
|
+
const bundle = defaultCompetition ? allFixtures() : [];
|
|
6722
|
+
const schedule = cachedFixtures.length ? mergeLive(bundle, cachedFixtures) : defaultCompetition ? void 0 : [];
|
|
6424
6723
|
if (team) {
|
|
6425
6724
|
const mine = live.find((m) => m.home?.code === team || m.away?.code === team);
|
|
6426
6725
|
if (mine) return `\u26BD ${matchSegment(mine, compact, flags)}`;
|
|
@@ -6514,7 +6813,7 @@ function fixturesStale(state, now) {
|
|
|
6514
6813
|
return fixturesAttemptAgeMs(state, now) > FIXTURES_EMPTY_TTL_MS;
|
|
6515
6814
|
}
|
|
6516
6815
|
function nextStaticUpcoming(nowMs) {
|
|
6517
|
-
return [...allFixtures()].sort(byKickoff).find((m) =>
|
|
6816
|
+
return [...allFixtures()].sort(byKickoff).find((m) => isUpcoming(m, new Date(nowMs)));
|
|
6518
6817
|
}
|
|
6519
6818
|
function inKnockoutPhase(nowMs) {
|
|
6520
6819
|
if (resolveCompetition() !== DEFAULT_COMPETITION) return false;
|
|
@@ -6525,8 +6824,8 @@ function liveWindowActive(nowMs) {
|
|
|
6525
6824
|
if (resolveCompetition() !== DEFAULT_COMPETITION) return true;
|
|
6526
6825
|
return inLiveWindow(nowMs);
|
|
6527
6826
|
}
|
|
6528
|
-
function liveAdapter(source) {
|
|
6529
|
-
return makeAdapter(source, { enrichGroups: false });
|
|
6827
|
+
function liveAdapter(source, now) {
|
|
6828
|
+
return makeAdapter(source, { enrichGroups: false, now });
|
|
6530
6829
|
}
|
|
6531
6830
|
async function runRefresh(opts = {}) {
|
|
6532
6831
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
@@ -6572,7 +6871,8 @@ async function runRefresh(opts = {}) {
|
|
|
6572
6871
|
}
|
|
6573
6872
|
return;
|
|
6574
6873
|
}
|
|
6575
|
-
|
|
6874
|
+
const token = claimLock();
|
|
6875
|
+
if (!token) return;
|
|
6576
6876
|
try {
|
|
6577
6877
|
let live = base?.live ?? [];
|
|
6578
6878
|
let degraded = base?.degraded ?? false;
|
|
@@ -6581,7 +6881,9 @@ async function runRefresh(opts = {}) {
|
|
|
6581
6881
|
let fixturesUpdatedAt = base?.fixturesUpdatedAt;
|
|
6582
6882
|
let fixturesAttemptedAt = base?.fixturesAttemptedAt;
|
|
6583
6883
|
let backoffUntil = base?.backoffUntil;
|
|
6584
|
-
const
|
|
6884
|
+
const realStart = Date.now();
|
|
6885
|
+
const clock = () => nowMs + (Date.now() - realStart);
|
|
6886
|
+
const adapter = liveAdapter(source, clock);
|
|
6585
6887
|
if (needLive) {
|
|
6586
6888
|
try {
|
|
6587
6889
|
const r = await getLiveMatches(adapter, now);
|
|
@@ -6603,26 +6905,32 @@ async function runRefresh(opts = {}) {
|
|
|
6603
6905
|
} catch {
|
|
6604
6906
|
}
|
|
6605
6907
|
}
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
).toISOString();
|
|
6908
|
+
const armed = adapter.cooldownUntil;
|
|
6909
|
+
if (armed !== void 0 && armed > clock()) {
|
|
6910
|
+
const jitter = opts.jitterMs ?? Math.floor(Math.random() * BACKOFF_JITTER_MS);
|
|
6911
|
+
backoffUntil = new Date(Math.max(armed, nowMs + BACKOFF_MS) + jitter).toISOString();
|
|
6610
6912
|
} else if (backoffUntil && Date.parse(backoffUntil) <= nowMs) {
|
|
6611
6913
|
backoffUntil = void 0;
|
|
6612
6914
|
}
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6616
|
-
|
|
6617
|
-
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6915
|
+
const published = publishState(
|
|
6916
|
+
{
|
|
6917
|
+
updatedAt,
|
|
6918
|
+
live,
|
|
6919
|
+
degraded,
|
|
6920
|
+
source,
|
|
6921
|
+
competition,
|
|
6922
|
+
...fixtures ? { fixtures } : {},
|
|
6923
|
+
...fixturesUpdatedAt ? { fixturesUpdatedAt } : {},
|
|
6924
|
+
...fixturesAttemptedAt ? { fixturesAttemptedAt } : {},
|
|
6925
|
+
...backoffUntil ? { backoffUntil } : {}
|
|
6926
|
+
},
|
|
6927
|
+
token
|
|
6928
|
+
);
|
|
6929
|
+
if (!published && process.env.CLAUDINHO_DEBUG) {
|
|
6930
|
+
process.stderr.write("claudinho: refresh lease lost to a successor; snapshot not published\n");
|
|
6931
|
+
}
|
|
6624
6932
|
} finally {
|
|
6625
|
-
releaseLock();
|
|
6933
|
+
releaseLock(token);
|
|
6626
6934
|
}
|
|
6627
6935
|
}
|
|
6628
6936
|
function shouldRefresh(now = Date.now(), state = readState("espn", resolveCompetition())) {
|
|
@@ -6654,7 +6962,7 @@ function spawnRefresh(source) {
|
|
|
6654
6962
|
}
|
|
6655
6963
|
|
|
6656
6964
|
// src/install.ts
|
|
6657
|
-
import { copyFileSync, existsSync, readFileSync as readFileSync5 } from "fs";
|
|
6965
|
+
import { copyFileSync, existsSync, lstatSync as lstatSync2, readFileSync as readFileSync5 } from "fs";
|
|
6658
6966
|
import { homedir as homedir2 } from "os";
|
|
6659
6967
|
import { join as join5 } from "path";
|
|
6660
6968
|
function claudeSettingsPath() {
|
|
@@ -6689,21 +6997,41 @@ function defaultStatusLineConfig(target, command) {
|
|
|
6689
6997
|
function restartMessage(target) {
|
|
6690
6998
|
return target === "cursor" ? "Restart Cursor CLI (or start a new session) to see it." : "Restart Claude Code to see it.";
|
|
6691
6999
|
}
|
|
7000
|
+
function isSettingsObject(v) {
|
|
7001
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
7002
|
+
}
|
|
7003
|
+
function manual(path, snippet, why) {
|
|
7004
|
+
return { action: "manual", path, message: `${why} ${path}. Add this manually:
|
|
7005
|
+
${snippet}` };
|
|
7006
|
+
}
|
|
6692
7007
|
function readSettings(path, snippet) {
|
|
6693
|
-
|
|
7008
|
+
let entry;
|
|
6694
7009
|
try {
|
|
6695
|
-
|
|
7010
|
+
entry = lstatSync2(path);
|
|
6696
7011
|
} catch {
|
|
6697
|
-
return {
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
7012
|
+
return {};
|
|
7013
|
+
}
|
|
7014
|
+
if (entry.isSymbolicLink() && !existsSync(path)) {
|
|
7015
|
+
return manual(path, snippet, "Settings path is a symlink to a missing file:");
|
|
7016
|
+
}
|
|
7017
|
+
let parsed;
|
|
7018
|
+
try {
|
|
7019
|
+
parsed = JSON.parse(readFileSync5(path, "utf8"));
|
|
7020
|
+
} catch {
|
|
7021
|
+
return manual(path, snippet, "Could not parse");
|
|
6703
7022
|
}
|
|
7023
|
+
if (!isSettingsObject(parsed)) return manual(path, snippet, "Not a JSON settings object:");
|
|
7024
|
+
return parsed;
|
|
6704
7025
|
}
|
|
6705
7026
|
function isInitResult(v) {
|
|
6706
|
-
return
|
|
7027
|
+
return typeof v.action === "string" && typeof v.path === "string" && typeof v.message === "string";
|
|
7028
|
+
}
|
|
7029
|
+
var SETTINGS_FILE_MODE = 384;
|
|
7030
|
+
var SETTINGS_WRITE = { mode: SETTINGS_FILE_MODE, followSymlinks: true };
|
|
7031
|
+
function validHookMatchers(slot) {
|
|
7032
|
+
return slot.every(
|
|
7033
|
+
(m) => isSettingsObject(m) && (m.hooks === void 0 || Array.isArray(m.hooks) && m.hooks.every(isSettingsObject))
|
|
7034
|
+
);
|
|
6707
7035
|
}
|
|
6708
7036
|
function initStatuslineFor(target, opts = {}) {
|
|
6709
7037
|
const path = configPathFor(target, opts.path);
|
|
@@ -6718,12 +7046,12 @@ function initStatuslineFor(target, opts = {}) {
|
|
|
6718
7046
|
const settings = parsed;
|
|
6719
7047
|
const existing = settings.statusLine;
|
|
6720
7048
|
if (isSameCommand(existing?.command, command)) {
|
|
6721
|
-
const
|
|
6722
|
-
return { action: "already", path, message: `${
|
|
7049
|
+
const label2 = target === "cursor" ? "Cursor CLI statusline" : "Statusline";
|
|
7050
|
+
return { action: "already", path, message: `${label2} already configured (${path}).` };
|
|
6723
7051
|
}
|
|
6724
7052
|
backupOnce(path);
|
|
6725
7053
|
settings.statusLine = sl;
|
|
6726
|
-
writeFileAtomic(path, JSON.stringify(settings, null, 2) + "\n");
|
|
7054
|
+
writeFileAtomic(path, JSON.stringify(settings, null, 2) + "\n", SETTINGS_WRITE);
|
|
6727
7055
|
const surface = target === "cursor" ? "Cursor CLI statusline" : "Statusline";
|
|
6728
7056
|
return {
|
|
6729
7057
|
action: "written",
|
|
@@ -6757,6 +7085,13 @@ function initHook(opts = {}) {
|
|
|
6757
7085
|
const parsed = readSettings(path, snippet);
|
|
6758
7086
|
if (isInitResult(parsed)) return parsed;
|
|
6759
7087
|
const settings = parsed;
|
|
7088
|
+
if (settings.hooks !== void 0 && !isSettingsObject(settings.hooks)) {
|
|
7089
|
+
return manual(path, snippet, 'Unexpected "hooks" shape in');
|
|
7090
|
+
}
|
|
7091
|
+
const eventSlot = settings.hooks?.[CLAUDE_HOOK_EVENT];
|
|
7092
|
+
if (eventSlot !== void 0 && (!Array.isArray(eventSlot) || !validHookMatchers(eventSlot))) {
|
|
7093
|
+
return manual(path, snippet, `Unexpected "hooks.${CLAUDE_HOOK_EVENT}" shape in`);
|
|
7094
|
+
}
|
|
6760
7095
|
if (claudeHookCommands(settings).some((c) => isSameCommand(c, command))) {
|
|
6761
7096
|
return {
|
|
6762
7097
|
action: "already",
|
|
@@ -6769,7 +7104,7 @@ function initHook(opts = {}) {
|
|
|
6769
7104
|
const hooks = settings.hooks;
|
|
6770
7105
|
hooks[CLAUDE_HOOK_EVENT] ??= [];
|
|
6771
7106
|
hooks[CLAUDE_HOOK_EVENT].push({ hooks: [{ type: "command", command }] });
|
|
6772
|
-
writeFileAtomic(path, JSON.stringify(settings, null, 2) + "\n");
|
|
7107
|
+
writeFileAtomic(path, JSON.stringify(settings, null, 2) + "\n", SETTINGS_WRITE);
|
|
6773
7108
|
return {
|
|
6774
7109
|
action: "written",
|
|
6775
7110
|
path,
|
|
@@ -6777,9 +7112,61 @@ function initHook(opts = {}) {
|
|
|
6777
7112
|
};
|
|
6778
7113
|
}
|
|
6779
7114
|
|
|
7115
|
+
// src/providerBackoff.ts
|
|
7116
|
+
function persistBackoff(source, competition, until, nowMs) {
|
|
7117
|
+
const token = claimLock(nowMs);
|
|
7118
|
+
if (!token) return false;
|
|
7119
|
+
try {
|
|
7120
|
+
const prior = readState(source, competition);
|
|
7121
|
+
const base = prior && prior.source === source && prior.competition === competition ? prior : {
|
|
7122
|
+
updatedAt: new Date(nowMs).toISOString(),
|
|
7123
|
+
live: [],
|
|
7124
|
+
degraded: true,
|
|
7125
|
+
// the provider refused us — never claim otherwise
|
|
7126
|
+
source,
|
|
7127
|
+
competition
|
|
7128
|
+
};
|
|
7129
|
+
return publishState({ ...base, backoffUntil: new Date(until).toISOString() }, token);
|
|
7130
|
+
} finally {
|
|
7131
|
+
releaseLock(token);
|
|
7132
|
+
}
|
|
7133
|
+
}
|
|
7134
|
+
function withPersistedBackoff(adapter, source, now = /* @__PURE__ */ new Date()) {
|
|
7135
|
+
const competition = resolveCompetition();
|
|
7136
|
+
const nowMs = now.getTime();
|
|
7137
|
+
const state = readCurrentState(source, competition);
|
|
7138
|
+
const accepted = state?.backoffUntil && backoffActive(state, nowMs) ? Date.parse(state.backoffUntil) : void 0;
|
|
7139
|
+
if (accepted !== void 0) adapter.armCooldown?.(accepted);
|
|
7140
|
+
let persisted = accepted;
|
|
7141
|
+
const persistIfNewer = (until) => {
|
|
7142
|
+
if (!(until > nowMs) || persisted !== void 0 && until <= persisted) return;
|
|
7143
|
+
if (persistBackoff(source, competition, until, nowMs)) persisted = until;
|
|
7144
|
+
};
|
|
7145
|
+
adapter.onCooldown?.(persistIfNewer);
|
|
7146
|
+
const afterCall = () => {
|
|
7147
|
+
const until = adapter.cooldownUntil;
|
|
7148
|
+
if (until !== void 0) persistIfNewer(until);
|
|
7149
|
+
};
|
|
7150
|
+
return new Proxy(adapter, {
|
|
7151
|
+
get(target, prop) {
|
|
7152
|
+
const value = Reflect.get(target, prop, target);
|
|
7153
|
+
if (typeof value === "function" && typeof prop === "string" && prop.startsWith("fetch")) {
|
|
7154
|
+
return async (...args) => {
|
|
7155
|
+
try {
|
|
7156
|
+
return await value.apply(target, args);
|
|
7157
|
+
} finally {
|
|
7158
|
+
afterCall();
|
|
7159
|
+
}
|
|
7160
|
+
};
|
|
7161
|
+
}
|
|
7162
|
+
return value;
|
|
7163
|
+
}
|
|
7164
|
+
});
|
|
7165
|
+
}
|
|
7166
|
+
|
|
6780
7167
|
// src/commands.ts
|
|
6781
|
-
function adapterFor({ cfg, adapter }) {
|
|
6782
|
-
return adapter ?? makeAdapter(cfg.source);
|
|
7168
|
+
function adapterFor({ cfg, adapter, now }) {
|
|
7169
|
+
return withPersistedBackoff(adapter ?? makeAdapter(cfg.source), cfg.source, now);
|
|
6783
7170
|
}
|
|
6784
7171
|
var DEFAULT_ON_MARKET_OPTS = { deadlineMs: 2e3, timeoutMs: 2500 };
|
|
6785
7172
|
var MARKETS_CMD_OPTS = { deadlineMs: 12e3, timeoutMs: 6e3 };
|
|
@@ -6958,20 +7345,30 @@ async function cmdNext(team, ctx) {
|
|
|
6958
7345
|
const { cfg, t: t2, now } = ctx;
|
|
6959
7346
|
precheck(cfg, t2);
|
|
6960
7347
|
const code = resolveTeamArg(team, "Usage: claudinho next <team> (or set CLAUDINHO_TEAM)", t2);
|
|
6961
|
-
const { fixture, degraded, source } = await getNextFixtureForTeam(
|
|
7348
|
+
const { fixture, degraded, source, unsupported } = await getNextFixtureForTeam(
|
|
6962
7349
|
adapterFor(ctx),
|
|
6963
7350
|
code,
|
|
6964
7351
|
now ?? /* @__PURE__ */ new Date()
|
|
6965
7352
|
);
|
|
6966
7353
|
if (cfg.json) {
|
|
6967
|
-
emitJson({
|
|
7354
|
+
emitJson({
|
|
7355
|
+
team: code,
|
|
7356
|
+
fixture: fixture ?? null,
|
|
7357
|
+
degraded,
|
|
7358
|
+
source: source ?? null,
|
|
7359
|
+
...unsupported ? { unsupported: true } : {}
|
|
7360
|
+
});
|
|
6968
7361
|
return;
|
|
6969
7362
|
}
|
|
6970
7363
|
const c = painterFor(cfg);
|
|
6971
7364
|
const flags = flagsEnabled();
|
|
6972
7365
|
out();
|
|
6973
7366
|
if (!fixture) {
|
|
6974
|
-
out(
|
|
7367
|
+
out(
|
|
7368
|
+
c.dim(
|
|
7369
|
+
" " + (unsupported ? t2("competition.unsupported") : degraded ? t2("live.degraded") : t2("next.none", { team: code }))
|
|
7370
|
+
)
|
|
7371
|
+
);
|
|
6975
7372
|
out();
|
|
6976
7373
|
out(disclaimer(t2, c));
|
|
6977
7374
|
endScoreCommand(ctx);
|
|
@@ -7003,7 +7400,7 @@ function cmdTeam(query, ctx) {
|
|
|
7003
7400
|
}
|
|
7004
7401
|
const c = painterFor(cfg);
|
|
7005
7402
|
const flags = flagsEnabled();
|
|
7006
|
-
const
|
|
7403
|
+
const label2 = (tm) => {
|
|
7007
7404
|
const flag2 = flags ? `${tm.flag} ` : "";
|
|
7008
7405
|
const grp = tm.group ? ` \xB7 ${t2("team.group", { group: tm.group })}` : "";
|
|
7009
7406
|
return ` ${flag2}${c.bold(tm.name)} ${c.dim(tm.code + grp)}`;
|
|
@@ -7012,10 +7409,10 @@ function cmdTeam(query, ctx) {
|
|
|
7012
7409
|
if (!q) {
|
|
7013
7410
|
out(" " + c.dim(t2("team.usage")));
|
|
7014
7411
|
} else if (team) {
|
|
7015
|
-
out(
|
|
7412
|
+
out(label2(team));
|
|
7016
7413
|
} else if (matches.length > 0) {
|
|
7017
7414
|
out(" " + c.dim(t2("team.ambiguous", { query: q })));
|
|
7018
|
-
for (const m of matches) out(
|
|
7415
|
+
for (const m of matches) out(label2(m));
|
|
7019
7416
|
} else {
|
|
7020
7417
|
out(" " + c.dim(t2("team.none", { query: q })));
|
|
7021
7418
|
}
|
|
@@ -7028,7 +7425,11 @@ async function cmdTable(group, ctx) {
|
|
|
7028
7425
|
precheck(cfg, t2);
|
|
7029
7426
|
const { tables, degraded, source } = await getStandings(adapterFor(ctx), group);
|
|
7030
7427
|
if (cfg.json) {
|
|
7031
|
-
const json = tables.map((tb) => ({
|
|
7428
|
+
const json = tables.map((tb) => ({
|
|
7429
|
+
group: tb.group,
|
|
7430
|
+
standings: tb.rows,
|
|
7431
|
+
...tb.partial ? { partial: tb.partial } : {}
|
|
7432
|
+
}));
|
|
7032
7433
|
emitJson({
|
|
7033
7434
|
degraded,
|
|
7034
7435
|
source: source ?? null,
|
|
@@ -7051,7 +7452,7 @@ async function cmdTable(group, ctx) {
|
|
|
7051
7452
|
out(disclaimer(t2, c));
|
|
7052
7453
|
return;
|
|
7053
7454
|
}
|
|
7054
|
-
for (const { group: g, rows } of tables) {
|
|
7455
|
+
for (const { group: g, rows, partial } of tables) {
|
|
7055
7456
|
out();
|
|
7056
7457
|
out(header(t2("table.title", { group: g }), c));
|
|
7057
7458
|
const table = new Table({
|
|
@@ -7079,6 +7480,7 @@ async function cmdTable(group, ctx) {
|
|
|
7079
7480
|
]);
|
|
7080
7481
|
}
|
|
7081
7482
|
out(table.toString());
|
|
7483
|
+
if (partial) out(c.dim(" " + t2("table.partial", { n: String(partial.omitted) })));
|
|
7082
7484
|
}
|
|
7083
7485
|
out();
|
|
7084
7486
|
if (degraded) out(c.dim(" " + t2("table.degraded")));
|
|
@@ -7095,10 +7497,22 @@ async function cmdBracket(stage, opts, ctx) {
|
|
|
7095
7497
|
if (filter && !BRACKET_STAGES.has(filter)) {
|
|
7096
7498
|
throw new InputError(t(cfg.lang, "bracket.invalidStage"));
|
|
7097
7499
|
}
|
|
7098
|
-
const { view, degraded, standingsDegraded, source } = await getBracket(
|
|
7500
|
+
const { view, degraded, standingsDegraded, source, unsupported } = await getBracket(
|
|
7099
7501
|
adapterFor(ctx),
|
|
7100
7502
|
filter ? { stage: filter, lang: cfg.lang } : { lang: cfg.lang }
|
|
7101
7503
|
);
|
|
7504
|
+
if (unsupported) {
|
|
7505
|
+
if (cfg.json) {
|
|
7506
|
+
emitJson({ degraded, standingsDegraded, source: null, view, unsupported: true });
|
|
7507
|
+
return;
|
|
7508
|
+
}
|
|
7509
|
+
const c2 = painterFor(cfg);
|
|
7510
|
+
out();
|
|
7511
|
+
out(c2.dim(` ${t(cfg.lang, "competition.unsupported")}`));
|
|
7512
|
+
out();
|
|
7513
|
+
out(disclaimer(t2, c2));
|
|
7514
|
+
return;
|
|
7515
|
+
}
|
|
7102
7516
|
if (cfg.json) {
|
|
7103
7517
|
emitJson({
|
|
7104
7518
|
degraded,
|
|
@@ -7263,7 +7677,7 @@ function cmdInitClaude(opts, { cfg }) {
|
|
|
7263
7677
|
async function cmdMatch(id, ctx) {
|
|
7264
7678
|
const { cfg, t: t2 } = ctx;
|
|
7265
7679
|
precheck(cfg, t2);
|
|
7266
|
-
const { match, degraded, source: liveSource } = await getMatchById(adapterFor(ctx), id);
|
|
7680
|
+
const { match, degraded, source: liveSource, unsupported } = await getMatchById(adapterFor(ctx), id);
|
|
7267
7681
|
const market = match ? await reliableMarketSignalFor(ctx, match) : { signal: void 0, complete: true };
|
|
7268
7682
|
if (cfg.json) {
|
|
7269
7683
|
emitJson({
|
|
@@ -7271,14 +7685,15 @@ async function cmdMatch(id, ctx) {
|
|
|
7271
7685
|
match: match ?? null,
|
|
7272
7686
|
source: liveSource ?? null,
|
|
7273
7687
|
marketComplete: market.complete,
|
|
7274
|
-
marketSignal: market.signal ?? null
|
|
7688
|
+
marketSignal: market.signal ?? null,
|
|
7689
|
+
...unsupported ? { unsupported: true } : {}
|
|
7275
7690
|
});
|
|
7276
7691
|
return;
|
|
7277
7692
|
}
|
|
7278
7693
|
const c = painterFor(cfg);
|
|
7279
7694
|
out();
|
|
7280
7695
|
if (!match) {
|
|
7281
|
-
out(c.dim(" " + t2("match.none", { id })));
|
|
7696
|
+
out(c.dim(" " + (unsupported ? t2("competition.unsupported") : t2("match.none", { id }))));
|
|
7282
7697
|
out();
|
|
7283
7698
|
out(disclaimer(t2, c));
|
|
7284
7699
|
return;
|
|
@@ -7337,7 +7752,7 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
7337
7752
|
precheck(cfg, t2);
|
|
7338
7753
|
const code = resolveTeamArg(team, "Usage: claudinho markets next <team> (or set CLAUDINHO_TEAM)", t2);
|
|
7339
7754
|
const now2 = ctx.now ?? /* @__PURE__ */ new Date();
|
|
7340
|
-
const { match: fixture, degraded } = await marketFixtureForTeam(adapterFor(ctx), code, now2);
|
|
7755
|
+
const { match: fixture, degraded, unsupported } = await marketFixtureForTeam(adapterFor(ctx), code, now2);
|
|
7341
7756
|
const market = fixture && marketRelevant(fixture, now2) ? await marketSignalsFor(ctx, [fixture], MARKETS_CMD_OPTS) : { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
7342
7757
|
const sig = fixture ? market.signals.get(fixture.id) : void 0;
|
|
7343
7758
|
const shown = market.complete && fixture && sig && marketDisplayable(fixture, sig) ? sig : void 0;
|
|
@@ -7348,14 +7763,21 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
7348
7763
|
degraded,
|
|
7349
7764
|
informationalOnly: true,
|
|
7350
7765
|
complete: market.complete,
|
|
7351
|
-
signal: shown ?? null
|
|
7766
|
+
signal: shown ?? null,
|
|
7767
|
+
// Review P2 on #129: a JSON consumer must tell "not available for this
|
|
7768
|
+
// competition" from a successful empty result; the text branch already did.
|
|
7769
|
+
...unsupported ? { unsupported: true } : {}
|
|
7352
7770
|
});
|
|
7353
7771
|
return;
|
|
7354
7772
|
}
|
|
7355
7773
|
const c2 = painterFor(cfg);
|
|
7356
7774
|
out();
|
|
7357
7775
|
if (!fixture) {
|
|
7358
|
-
out(
|
|
7776
|
+
out(
|
|
7777
|
+
c2.dim(
|
|
7778
|
+
" " + (unsupported ? t2("competition.unsupported") : degraded ? t2("live.degraded") : t2("next.none", { team: code }))
|
|
7779
|
+
)
|
|
7780
|
+
);
|
|
7359
7781
|
} else {
|
|
7360
7782
|
out(header(marketHeaderLine(fixture, cfg), c2));
|
|
7361
7783
|
out();
|
|
@@ -7372,7 +7794,7 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
7372
7794
|
if (target && target !== "today" && !isValidDate(target)) {
|
|
7373
7795
|
precheck(cfg, t2);
|
|
7374
7796
|
const now2 = ctx.now ?? /* @__PURE__ */ new Date();
|
|
7375
|
-
const { match } = await getMatchById(adapterFor(ctx), target);
|
|
7797
|
+
const { match, unsupported } = await getMatchById(adapterFor(ctx), target);
|
|
7376
7798
|
const market = match && marketRelevant(match, now2) ? await marketSignalsFor(ctx, [match], MARKETS_CMD_OPTS) : { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
7377
7799
|
const sig = match ? market.signals.get(match.id) : void 0;
|
|
7378
7800
|
const shown = market.complete && match && sig && marketDisplayable(match, sig) ? sig : void 0;
|
|
@@ -7381,14 +7803,15 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
7381
7803
|
matchId: target,
|
|
7382
7804
|
informationalOnly: true,
|
|
7383
7805
|
complete: market.complete,
|
|
7384
|
-
signal: shown ?? null
|
|
7806
|
+
signal: shown ?? null,
|
|
7807
|
+
...unsupported ? { unsupported: true } : {}
|
|
7385
7808
|
});
|
|
7386
7809
|
return;
|
|
7387
7810
|
}
|
|
7388
7811
|
const c2 = painterFor(cfg);
|
|
7389
7812
|
out();
|
|
7390
7813
|
if (!match) {
|
|
7391
|
-
out(c2.dim(" " + t2("match.none", { id: target })));
|
|
7814
|
+
out(c2.dim(" " + (unsupported ? t2("competition.unsupported") : t2("match.none", { id: target }))));
|
|
7392
7815
|
} else {
|
|
7393
7816
|
out(header(marketHeaderLine(match, cfg), c2));
|
|
7394
7817
|
out();
|
|
@@ -7469,7 +7892,9 @@ function emitShare(ctx, e, copy) {
|
|
|
7469
7892
|
snippet,
|
|
7470
7893
|
matches: e.input.matches,
|
|
7471
7894
|
marketComplete: e.input.marketComplete ?? true,
|
|
7472
|
-
marketSignals: Object.fromEntries(e.input.marketSignals ?? /* @__PURE__ */ new Map())
|
|
7895
|
+
marketSignals: Object.fromEntries(e.input.marketSignals ?? /* @__PURE__ */ new Map()),
|
|
7896
|
+
// The structured card keeps the verdict the snippet's note carries.
|
|
7897
|
+
...e.unsupported ? { unsupported: true } : {}
|
|
7473
7898
|
});
|
|
7474
7899
|
} else {
|
|
7475
7900
|
out(snippet);
|
|
@@ -7501,7 +7926,12 @@ function emitShareTable(ctx, e, copy) {
|
|
|
7501
7926
|
degraded: e.degraded,
|
|
7502
7927
|
informationalOnly: true,
|
|
7503
7928
|
snippet,
|
|
7504
|
-
|
|
7929
|
+
// The structured card keeps the verdict the snippet warns about (A01).
|
|
7930
|
+
tables: e.tables.map((tb) => ({
|
|
7931
|
+
group: tb.group,
|
|
7932
|
+
standings: tb.rows,
|
|
7933
|
+
...tb.partial ? { partial: tb.partial } : {}
|
|
7934
|
+
}))
|
|
7505
7935
|
});
|
|
7506
7936
|
} else {
|
|
7507
7937
|
out(snippet);
|
|
@@ -7532,7 +7962,8 @@ function emitShareBracket(ctx, e, copy) {
|
|
|
7532
7962
|
degraded: e.degraded,
|
|
7533
7963
|
informationalOnly: true,
|
|
7534
7964
|
snippet,
|
|
7535
|
-
view: e.view
|
|
7965
|
+
view: e.view,
|
|
7966
|
+
...e.unsupported ? { unsupported: true } : {}
|
|
7536
7967
|
});
|
|
7537
7968
|
} else {
|
|
7538
7969
|
out(snippet);
|
|
@@ -7606,7 +8037,7 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
7606
8037
|
if (stageFilter && !BRACKET_STAGES.has(stageFilter)) {
|
|
7607
8038
|
throw new InputError(t(cfg.lang, "bracket.invalidStage"));
|
|
7608
8039
|
}
|
|
7609
|
-
const { view, degraded: degraded2, source: source2 } = await getBracket(
|
|
8040
|
+
const { view, degraded: degraded2, source: source2, unsupported } = await getBracket(
|
|
7610
8041
|
adapterFor(ctx),
|
|
7611
8042
|
stageFilter ? { stage: stageFilter, lang: cfg.lang } : { lang: cfg.lang }
|
|
7612
8043
|
);
|
|
@@ -7618,7 +8049,8 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
7618
8049
|
source: degraded2 ? void 0 : source2,
|
|
7619
8050
|
degraded: degraded2,
|
|
7620
8051
|
installLine: stageFilter ? `npx @claudinho/cli bracket ${stageFilter}` : "npx @claudinho/cli bracket",
|
|
7621
|
-
emptyNote: t(cfg.lang, "bracket.empty"),
|
|
8052
|
+
emptyNote: unsupported ? t(cfg.lang, "competition.unsupported") : t(cfg.lang, "bracket.empty"),
|
|
8053
|
+
unsupported,
|
|
7622
8054
|
options: {
|
|
7623
8055
|
includeHashtag: baseOptions.includeHashtag,
|
|
7624
8056
|
includeInstallLine: baseOptions.includeInstallLine,
|
|
@@ -7634,7 +8066,7 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
7634
8066
|
if (target === "next") {
|
|
7635
8067
|
precheck(cfg, t2);
|
|
7636
8068
|
const code = resolveTeamArg(team, "Usage: claudinho share next <team> (or set CLAUDINHO_TEAM)", t2);
|
|
7637
|
-
const { fixture, degraded: degraded2, source: source2 } = await getNextFixtureForTeam(
|
|
8069
|
+
const { fixture, degraded: degraded2, source: source2, unsupported } = await getNextFixtureForTeam(
|
|
7638
8070
|
adapterFor(ctx),
|
|
7639
8071
|
code,
|
|
7640
8072
|
ctx.now ?? /* @__PURE__ */ new Date()
|
|
@@ -7658,12 +8090,13 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
7658
8090
|
source: source2,
|
|
7659
8091
|
degraded: degraded2,
|
|
7660
8092
|
// Fail-closed: an outage must never paste as "no fixture" (eliminated).
|
|
7661
|
-
emptyNote: degraded2 ? `Couldn't reach the data provider \u2014 no upcoming fixture confirmed for ${code}.` : `No upcoming fixture found for ${code}.`,
|
|
8093
|
+
emptyNote: unsupported ? t(cfg.lang, "competition.unsupported") : degraded2 ? `Couldn't reach the data provider \u2014 no upcoming fixture confirmed for ${code}.` : `No upcoming fixture found for ${code}.`,
|
|
7662
8094
|
installLine: `npx @claudinho/cli next ${code}`,
|
|
7663
8095
|
tz: cfg.tz,
|
|
7664
8096
|
locale: cfg.lang
|
|
7665
8097
|
},
|
|
7666
|
-
options: baseOptions
|
|
8098
|
+
options: baseOptions,
|
|
8099
|
+
unsupported
|
|
7667
8100
|
},
|
|
7668
8101
|
copy
|
|
7669
8102
|
);
|
|
@@ -7671,7 +8104,7 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
7671
8104
|
}
|
|
7672
8105
|
if (target && target !== "today" && !isValidDate(target)) {
|
|
7673
8106
|
precheck(cfg, t2);
|
|
7674
|
-
const { match, degraded: degraded2, source: source2 } = await getMatchById(adapterFor(ctx), target);
|
|
8107
|
+
const { match, degraded: degraded2, source: source2, unsupported } = await getMatchById(adapterFor(ctx), target);
|
|
7675
8108
|
const matches = match ? [match] : [];
|
|
7676
8109
|
const market2 = await reliableShareSignals(ctx, matches);
|
|
7677
8110
|
emitShare(
|
|
@@ -7686,12 +8119,13 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
7686
8119
|
marketComplete: market2.complete,
|
|
7687
8120
|
source: source2,
|
|
7688
8121
|
degraded: degraded2,
|
|
7689
|
-
emptyNote: `No match found with id ${target}.`,
|
|
8122
|
+
emptyNote: unsupported ? t(cfg.lang, "competition.unsupported") : `No match found with id ${target}.`,
|
|
7690
8123
|
installLine: `npx @claudinho/cli match ${target}`,
|
|
7691
8124
|
tz: cfg.tz,
|
|
7692
8125
|
locale: cfg.lang
|
|
7693
8126
|
},
|
|
7694
|
-
options: baseOptions
|
|
8127
|
+
options: baseOptions,
|
|
8128
|
+
unsupported
|
|
7695
8129
|
},
|
|
7696
8130
|
copy
|
|
7697
8131
|
);
|
|
@@ -7843,7 +8277,7 @@ function handlePipeError(stream) {
|
|
|
7843
8277
|
}
|
|
7844
8278
|
handlePipeError(process.stdout);
|
|
7845
8279
|
handlePipeError(process.stderr);
|
|
7846
|
-
var VERSION = "0.10.
|
|
8280
|
+
var VERSION = "0.10.1";
|
|
7847
8281
|
var DISCLAIMER = "Claudinho is an independent fan project. Not affiliated with or endorsed by FIFA or Anthropic.";
|
|
7848
8282
|
function ctxFrom(cmd) {
|
|
7849
8283
|
let root = cmd;
|