@claudinho/cli 0.8.6 → 0.8.8

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 +119 -18
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -2762,6 +2762,10 @@ var schedule_2026_default = [
2762
2762
  updatedAt: "2026-06-24T03:57:24.994Z"
2763
2763
  }
2764
2764
  ];
2765
+ var PLACEHOLDER_FLAG = "\u{1F3F3}\uFE0F";
2766
+ function isResolvedNation(team) {
2767
+ return team.flag !== PLACEHOLDER_FLAG;
2768
+ }
2765
2769
  var SCHEDULE = schedule_2026_default;
2766
2770
  function allFixtures() {
2767
2771
  return SCHEDULE;
@@ -3842,6 +3846,36 @@ async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Dat
3842
3846
  const next = nextFixtureForTeam(code, { from: now });
3843
3847
  return { match: next, degraded: false };
3844
3848
  }
3849
+ async function getNextFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Date()) {
3850
+ const base = allFixtures();
3851
+ let matches = base;
3852
+ let degraded = true;
3853
+ let liveById;
3854
+ try {
3855
+ const live = adapter.fetchWindow ? await adapter.fetchWindow(KNOCKOUT_WINDOW_START, KNOCKOUT_WINDOW_END) : [];
3856
+ matches = mergeLive(base, live);
3857
+ degraded = false;
3858
+ liveById = new Set(live.map((m) => m.id));
3859
+ } catch {
3860
+ }
3861
+ const fixture = nextFixtureForTeam(code, { from: now, fixtures: matches });
3862
+ const source = fixture && liveById?.has(fixture.id) ? adapter.name : void 0;
3863
+ return { fixture, degraded, source };
3864
+ }
3865
+ async function getKnockoutFixtures(adapter, now = /* @__PURE__ */ new Date()) {
3866
+ if (!adapter.fetchWindow) return { fixtures: [], degraded: true };
3867
+ let live;
3868
+ try {
3869
+ live = await adapter.fetchWindow(KNOCKOUT_WINDOW_START, KNOCKOUT_WINDOW_END);
3870
+ } catch {
3871
+ return { fixtures: [], degraded: true };
3872
+ }
3873
+ const nowMs = now.getTime();
3874
+ const fixtures = live.filter(
3875
+ (m) => m.stage !== "GROUP" && m.stage !== "FRIENDLY" && Date.parse(m.kickoff) >= nowMs && isResolvedNation(m.home) && isResolvedNation(m.away)
3876
+ ).sort(byKickoff);
3877
+ return { fixtures, degraded: false };
3878
+ }
3845
3879
  async function getMatchById(adapter, id) {
3846
3880
  const base = allFixtures().find((m) => m.id === id);
3847
3881
  if (!base) return { match: void 0, degraded: false };
@@ -4939,6 +4973,11 @@ function ageMs(state, now = Date.now()) {
4939
4973
  const t2 = Date.parse(state.updatedAt);
4940
4974
  return Number.isFinite(t2) ? now - t2 : Infinity;
4941
4975
  }
4976
+ function fixturesAgeMs(state, now = Date.now()) {
4977
+ if (!state?.fixturesUpdatedAt) return Infinity;
4978
+ const t2 = Date.parse(state.fixturesUpdatedAt);
4979
+ return Number.isFinite(t2) ? now - t2 : Infinity;
4980
+ }
4942
4981
  function lockAgeMs(now = Date.now()) {
4943
4982
  const lp = lockPath();
4944
4983
  try {
@@ -5010,8 +5049,11 @@ var LIVE_TTL_MS = 15e3;
5010
5049
  function inLiveWindow(now = Date.now(), fixtures = allFixtures()) {
5011
5050
  return fixturesInLiveWindow(now, fixtures).length > 0;
5012
5051
  }
5052
+ function isResolvedFixture(m) {
5053
+ return isResolvedNation(m.home) && isResolvedNation(m.away);
5054
+ }
5013
5055
  function nextOverall(now, fixtures = allFixtures()) {
5014
- return [...fixtures].sort(byKickoff).find((m) => Date.parse(m.kickoff) >= now);
5056
+ return [...fixtures].sort(byKickoff).find((m) => Date.parse(m.kickoff) >= now && isResolvedFixture(m));
5015
5057
  }
5016
5058
  function teamTok(t2, flags) {
5017
5059
  return flags ? t2.flag : t2.code;
@@ -5061,8 +5103,10 @@ function renderPrompt(state, opts = {}) {
5061
5103
  return `\u26BD ${teamTok(first.home, flags)} vs ${teamTok(first.away, flags)} live \xB7 syncing\u2026` + (more > 0 ? ` +${more}` : "");
5062
5104
  }
5063
5105
  }
5064
- const next = team ? nextFixtureForTeam(team, { from: now }) : nextOverall(nowMs);
5065
- if (next) {
5106
+ const cachedFixtures = Array.isArray(state?.fixtures) ? state.fixtures : [];
5107
+ const schedule = cachedFixtures.length ? mergeLive(allFixtures(), cachedFixtures) : void 0;
5108
+ const next = team ? nextFixtureForTeam(team, { from: now, fixtures: schedule }) : nextOverall(nowMs, schedule);
5109
+ if (next && isResolvedFixture(next)) {
5066
5110
  return `${teamTok(next.home, flags)} vs ${teamTok(next.away, flags)} in ${countdown(next.kickoff, now)}`;
5067
5111
  }
5068
5112
  return "\u26BD \u2014";
@@ -5096,6 +5140,15 @@ ${lines}`;
5096
5140
  // src/refresh.ts
5097
5141
  import { spawn } from "child_process";
5098
5142
  var MIN_REFRESH_MS = 12e3;
5143
+ var FIXTURES_TTL_MS = 15 * 6e4;
5144
+ function nextStaticUpcoming(nowMs) {
5145
+ return [...allFixtures()].sort(byKickoff).find((m) => Date.parse(m.kickoff) >= nowMs);
5146
+ }
5147
+ function inKnockoutPhase(nowMs) {
5148
+ if (resolveCompetition() !== DEFAULT_COMPETITION) return false;
5149
+ const next = nextStaticUpcoming(nowMs);
5150
+ return !!next && next.stage !== "GROUP" && next.stage !== "FRIENDLY";
5151
+ }
5099
5152
  function liveWindowActive(nowMs) {
5100
5153
  if (resolveCompetition() !== DEFAULT_COMPETITION) return true;
5101
5154
  return inLiveWindow(nowMs);
@@ -5109,17 +5162,23 @@ function liveAdapter() {
5109
5162
  }
5110
5163
  async function runRefresh(opts = {}) {
5111
5164
  const now = opts.now ?? /* @__PURE__ */ new Date();
5165
+ const nowMs = now.getTime();
5112
5166
  const source = opts.source ?? "espn";
5113
5167
  const competition = resolveCompetition();
5114
5168
  const cached = readState();
5115
- if (cached && cached.source === source && cached.competition === competition && ageMs(cached, now.getTime()) < MIN_REFRESH_MS) {
5116
- return;
5117
- }
5169
+ const sameScope = !!cached && cached.source === source && cached.competition === competition;
5170
+ const base = sameScope ? cached : void 0;
5171
+ const needLive = liveWindowActive(nowMs) && (!base || ageMs(base, nowMs) >= MIN_REFRESH_MS);
5172
+ const needFixtures = inKnockoutPhase(nowMs) && (!base || fixturesAgeMs(base, nowMs) >= FIXTURES_TTL_MS);
5173
+ if (!needLive && !needFixtures) return;
5118
5174
  if (!acquireLock()) return;
5119
5175
  try {
5120
- let live = [];
5121
- let degraded = false;
5122
- if (liveWindowActive(now.getTime())) {
5176
+ let live = base?.live ?? [];
5177
+ let degraded = base?.degraded ?? false;
5178
+ let updatedAt = base?.updatedAt ?? now.toISOString();
5179
+ let fixtures = base?.fixtures;
5180
+ let fixturesUpdatedAt = base?.fixturesUpdatedAt;
5181
+ if (needLive) {
5123
5182
  try {
5124
5183
  const r = await getLiveMatches(liveAdapter(), now);
5125
5184
  live = r.matches;
@@ -5127,8 +5186,27 @@ async function runRefresh(opts = {}) {
5127
5186
  } catch {
5128
5187
  degraded = true;
5129
5188
  }
5189
+ updatedAt = now.toISOString();
5190
+ }
5191
+ if (needFixtures) {
5192
+ try {
5193
+ const r = await getKnockoutFixtures(liveAdapter(), now);
5194
+ if (!r.degraded) {
5195
+ fixtures = r.fixtures;
5196
+ fixturesUpdatedAt = now.toISOString();
5197
+ }
5198
+ } catch {
5199
+ }
5130
5200
  }
5131
- writeState({ updatedAt: now.toISOString(), live, degraded, source, competition });
5201
+ writeState({
5202
+ updatedAt,
5203
+ live,
5204
+ degraded,
5205
+ source,
5206
+ competition,
5207
+ ...fixtures ? { fixtures } : {},
5208
+ ...fixturesUpdatedAt ? { fixturesUpdatedAt } : {}
5209
+ });
5132
5210
  } finally {
5133
5211
  releaseLock();
5134
5212
  }
@@ -5138,6 +5216,11 @@ function shouldRefresh(now = Date.now()) {
5138
5216
  if (isLockFresh(now)) return false;
5139
5217
  return ageMs(readState(), now) > LIVE_TTL_MS;
5140
5218
  }
5219
+ function shouldRefreshFixtures(now = Date.now(), state = readState()) {
5220
+ if (!inKnockoutPhase(now)) return false;
5221
+ if (isLockFresh(now)) return false;
5222
+ return fixturesAgeMs(state, now) > FIXTURES_TTL_MS;
5223
+ }
5141
5224
  function spawnRefresh(source) {
5142
5225
  try {
5143
5226
  const entry = process.argv[1];
@@ -5477,19 +5560,24 @@ async function cmdLive(ctx) {
5477
5560
  if (src) out(src);
5478
5561
  out(disclaimer(t2, c));
5479
5562
  }
5480
- async function cmdNext(team, { cfg, t: t2, now }) {
5563
+ async function cmdNext(team, ctx) {
5564
+ const { cfg, t: t2, now } = ctx;
5481
5565
  precheck(cfg, t2);
5482
5566
  const code = resolveTeamArg(team, "Usage: claudinho next <team> (or set CLAUDINHO_TEAM)");
5483
- const fixture = nextFixtureForTeam(code, { from: now ?? /* @__PURE__ */ new Date() });
5567
+ const { fixture, degraded, source } = await getNextFixtureForTeam(
5568
+ adapterFor(ctx),
5569
+ code,
5570
+ now ?? /* @__PURE__ */ new Date()
5571
+ );
5484
5572
  if (cfg.json) {
5485
- emitJson({ team: code, fixture: fixture ?? null });
5573
+ emitJson({ team: code, fixture: fixture ?? null, degraded, source: source ?? null });
5486
5574
  return;
5487
5575
  }
5488
5576
  const c = painterFor(cfg);
5489
5577
  const flags = flagsEnabled();
5490
5578
  out();
5491
5579
  if (!fixture) {
5492
- out(c.dim(" " + t2("next.none", { team: code })));
5580
+ out(c.dim(" " + (degraded ? t2("live.degraded") : t2("next.none", { team: code }))));
5493
5581
  out();
5494
5582
  out(disclaimer(t2, c));
5495
5583
  return;
@@ -5504,6 +5592,8 @@ async function cmdNext(team, { cfg, t: t2, now }) {
5504
5592
  )
5505
5593
  );
5506
5594
  out();
5595
+ const src = dataSource(source, cfg.lang, c);
5596
+ if (src) out(src);
5507
5597
  out(disclaimer(t2, c));
5508
5598
  }
5509
5599
  async function cmdTable(group, ctx) {
@@ -5631,7 +5721,9 @@ function cmdPrompt({ cfg }) {
5631
5721
  const state = readCurrentState(cfg.source, resolveCompetition());
5632
5722
  const scoreLine = renderPrompt(state, { team, compact, max, flags: flagsEnabled() });
5633
5723
  out(renderPromptOutput(scoreLine, payload));
5634
- if (!state || shouldRefresh()) spawnRefresh(cfg.source);
5724
+ if (!state || shouldRefresh() || shouldRefreshFixtures(Date.now(), state)) {
5725
+ spawnRefresh(cfg.source);
5726
+ }
5635
5727
  } catch {
5636
5728
  out("");
5637
5729
  }
@@ -6051,7 +6143,11 @@ async function cmdShare(target, team, opts, ctx) {
6051
6143
  if (target === "next") {
6052
6144
  precheck(cfg, t2);
6053
6145
  const code = resolveTeamArg(team, "Usage: claudinho share next <team> (or set CLAUDINHO_TEAM)");
6054
- const fixture = nextFixtureForTeam(code, { from: ctx.now ?? /* @__PURE__ */ new Date() });
6146
+ const { fixture, degraded: degraded2, source: source2 } = await getNextFixtureForTeam(
6147
+ adapterFor(ctx),
6148
+ code,
6149
+ ctx.now ?? /* @__PURE__ */ new Date()
6150
+ );
6055
6151
  const matches = fixture ? [fixture] : [];
6056
6152
  const signals2 = await reliableShareSignals(ctx, matches);
6057
6153
  const teamName = fixture ? fixture.home.code === code ? fixture.home.name : fixture.away.name : code;
@@ -6065,7 +6161,12 @@ async function cmdShare(target, team, opts, ctx) {
6065
6161
  title: `Next up for ${teamName}`,
6066
6162
  matches,
6067
6163
  marketSignals: signals2,
6068
- emptyNote: `No upcoming fixture found for ${code}.`,
6164
+ // Attribute the provider when the overlay resolved the tie (knockout);
6165
+ // undefined for a static group fixture — parity with CLI `next`.
6166
+ source: source2,
6167
+ degraded: degraded2,
6168
+ // Fail-closed: an outage must never paste as "no fixture" (eliminated).
6169
+ emptyNote: degraded2 ? `Couldn't reach the data provider \u2014 no upcoming fixture confirmed for ${code}.` : `No upcoming fixture found for ${code}.`,
6069
6170
  installLine: `npx @claudinho/cli next ${code}`,
6070
6171
  tz: cfg.tz,
6071
6172
  locale: cfg.lang
@@ -6202,7 +6303,7 @@ function handlePipeError(stream) {
6202
6303
  }
6203
6304
  handlePipeError(process.stdout);
6204
6305
  handlePipeError(process.stderr);
6205
- var VERSION = "0.8.6";
6306
+ var VERSION = "0.8.8";
6206
6307
  var DISCLAIMER = "Claudinho is an independent fan project. Not affiliated with or endorsed by FIFA or Anthropic.";
6207
6308
  function ctxFrom(cmd) {
6208
6309
  let root = cmd;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudinho/cli",
3
- "version": "0.8.6",
3
+ "version": "0.8.8",
4
4
  "description": "Live scores, fixtures, group tables, and read-only prediction-market signals for the 2026 men's football tournament — in your terminal, Claude Code, and Cursor CLI statusline. No API keys. Not affiliated with FIFA or Anthropic.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -61,7 +61,7 @@
61
61
  "tsup": "^8.0.0",
62
62
  "typescript": "^5.7.0",
63
63
  "vitest": "^4.1.9",
64
- "@claudinho/core": "0.8.6"
64
+ "@claudinho/core": "0.8.8"
65
65
  },
66
66
  "scripts": {
67
67
  "build": "tsup",