@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.
Files changed (2) hide show
  1. package/dist/index.js +613 -179
  2. 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(label) {
2984
- return label !== "" && displayWidth(label) === 0 ? "" : label;
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.kind === "team" && a.kind === "team" && h.providerId === a.providerId) {
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 label = humanLabel(child?.name ?? child?.abbreviation);
3434
- const letter = label.match(/Group\s+([A-L])/i)?.[1]?.toUpperCase();
3435
- if (!letter) continue;
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({ group: letter, rows: ranked.map((x) => x.row) });
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.0"} (+https://github.com/arturogarrido/claudinho)`;
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
- throw new ProviderError(
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
- const length = Number(res.headers?.get?.("content-length"));
3671
- if (Number.isFinite(length) && length > MAX_RESPONSE_BYTES) {
3672
- throw new ProviderError(`ESPN response too large: ${length} bytes`, "parse");
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.json();
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
- const n = table?.rows.length ?? 0;
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
- const required = matchesPerTeamInGroup(n);
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(tables.find((t2) => t2.group === group));
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(label) {
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 label = ref.position === 1 ? t(ctx.lang, "bracket.slot.groupWinner", { group: ref.group }) : t(ctx.lang, "bracket.slot.groupSecond", { group: ref.group });
3789
- return tbd(label);
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
- try {
4490
- const win = knockoutWindow();
4491
- const live = adapter.fetchWindow && win ? await adapter.fetchWindow(win.start, win.end) : [];
4492
- matches = mergeLive(base, live);
4493
- liveDegraded = false;
4494
- source = adapter.name;
4495
- } catch {
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
- try {
4524
- const win = knockoutWindow();
4525
- if (adapter.fetchWindow && win) {
4526
- fixtures = mergeLive(
4527
- fixtures,
4528
- await adapter.fetchWindow(win.start, win.end)
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
- } catch {
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
- try {
4552
- const win = knockoutWindow();
4553
- const live = adapter.fetchWindow && win ? await adapter.fetchWindow(win.start, win.end) : [];
4554
- matches = mergeLive(base, live);
4555
- degraded = false;
4556
- liveById = new Set(live.map((m) => m.id));
4557
- } catch {
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" && Date.parse(m.kickoff) >= nowMs && isResolvedNation(m.home) && isResolvedNation(m.away)
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
- const length = Number(res.headers?.get?.("content-length"));
5020
- if (Number.isFinite(length) && length > MAX_RESPONSE_BYTES) {
5021
- throw new Error(`Polymarket response too large: ${length} bytes`);
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, label] of legs) {
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
- blocks.push(
5432
- [`Group ${group} \xB7 standings`, "", ...rows.map((r, i) => tableRow(r, i + 1))].join("\n")
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
- let label = model;
5672
- if (payload.model?.param_summary) label += ` ${payload.model.param_summary}`;
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
- if (payload.worktree?.name) parts.push(`wt ${payload.worktree.name}`);
5680
- if (payload.vim?.mode) parts.push(payload.vim.mode);
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 statSync2 } from "fs";
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 { closeSync, mkdirSync, openSync, renameSync, writeSync } from "fs";
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
- const tmp = `${path}.${process.pid}.tmp`;
5972
- const fd = openSync(tmp, "w");
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
- } finally {
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 = statSync(path);
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(statSync(lp).mtimeMs).toISOString(), now);
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
- function acquireLock(now = Date.now()) {
6095
- mkdirSync2(cacheDir(), { recursive: true });
6096
- const lp = lockPath();
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, `${process.pid} ${now}`);
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 releaseLock() {
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
- rmSync(lockPath(), { force: true });
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 = statSync2(path);
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) => Date.parse(m.kickoff) >= now && isResolvedFixture(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 schedule = cachedFixtures.length ? mergeLive(allFixtures(), cachedFixtures) : void 0;
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) => Date.parse(m.kickoff) >= nowMs);
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
- if (!acquireLock()) return;
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 adapter = liveAdapter(source);
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
- if (adapter.lastError?.throttled) {
6607
- backoffUntil = new Date(
6608
- nowMs + BACKOFF_MS + Math.floor(Math.random() * BACKOFF_JITTER_MS)
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
- writeState({
6614
- updatedAt,
6615
- live,
6616
- degraded,
6617
- source,
6618
- competition,
6619
- ...fixtures ? { fixtures } : {},
6620
- ...fixturesUpdatedAt ? { fixturesUpdatedAt } : {},
6621
- ...fixturesAttemptedAt ? { fixturesAttemptedAt } : {},
6622
- ...backoffUntil ? { backoffUntil } : {}
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
- if (!existsSync(path)) return {};
7008
+ let entry;
6694
7009
  try {
6695
- return JSON.parse(readFileSync5(path, "utf8"));
7010
+ entry = lstatSync2(path);
6696
7011
  } catch {
6697
- return {
6698
- action: "manual",
6699
- path,
6700
- message: `Could not parse ${path}. Add this manually:
6701
- ${snippet}`
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 "action" in v && typeof v.action === "string";
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 label = target === "cursor" ? "Cursor CLI statusline" : "Statusline";
6722
- return { action: "already", path, message: `${label} already configured (${path}).` };
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({ team: code, fixture: fixture ?? null, degraded, source: source ?? null });
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(c.dim(" " + (degraded ? t2("live.degraded") : t2("next.none", { team: code }))));
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 label = (tm) => {
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(label(team));
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(label(m));
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) => ({ group: tb.group, standings: tb.rows }));
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(c2.dim(" " + (degraded ? t2("live.degraded") : t2("next.none", { team: code }))));
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
- tables: e.tables.map((tb) => ({ group: tb.group, standings: tb.rows }))
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.0";
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;