@claudinho/cli 0.8.7 → 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 +81 -12
  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;
@@ -3858,6 +3862,20 @@ async function getNextFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Da
3858
3862
  const source = fixture && liveById?.has(fixture.id) ? adapter.name : void 0;
3859
3863
  return { fixture, degraded, source };
3860
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
+ }
3861
3879
  async function getMatchById(adapter, id) {
3862
3880
  const base = allFixtures().find((m) => m.id === id);
3863
3881
  if (!base) return { match: void 0, degraded: false };
@@ -4955,6 +4973,11 @@ function ageMs(state, now = Date.now()) {
4955
4973
  const t2 = Date.parse(state.updatedAt);
4956
4974
  return Number.isFinite(t2) ? now - t2 : Infinity;
4957
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
+ }
4958
4981
  function lockAgeMs(now = Date.now()) {
4959
4982
  const lp = lockPath();
4960
4983
  try {
@@ -5026,8 +5049,11 @@ var LIVE_TTL_MS = 15e3;
5026
5049
  function inLiveWindow(now = Date.now(), fixtures = allFixtures()) {
5027
5050
  return fixturesInLiveWindow(now, fixtures).length > 0;
5028
5051
  }
5052
+ function isResolvedFixture(m) {
5053
+ return isResolvedNation(m.home) && isResolvedNation(m.away);
5054
+ }
5029
5055
  function nextOverall(now, fixtures = allFixtures()) {
5030
- 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));
5031
5057
  }
5032
5058
  function teamTok(t2, flags) {
5033
5059
  return flags ? t2.flag : t2.code;
@@ -5077,8 +5103,10 @@ function renderPrompt(state, opts = {}) {
5077
5103
  return `\u26BD ${teamTok(first.home, flags)} vs ${teamTok(first.away, flags)} live \xB7 syncing\u2026` + (more > 0 ? ` +${more}` : "");
5078
5104
  }
5079
5105
  }
5080
- const next = team ? nextFixtureForTeam(team, { from: now }) : nextOverall(nowMs);
5081
- 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)) {
5082
5110
  return `${teamTok(next.home, flags)} vs ${teamTok(next.away, flags)} in ${countdown(next.kickoff, now)}`;
5083
5111
  }
5084
5112
  return "\u26BD \u2014";
@@ -5112,6 +5140,15 @@ ${lines}`;
5112
5140
  // src/refresh.ts
5113
5141
  import { spawn } from "child_process";
5114
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
+ }
5115
5152
  function liveWindowActive(nowMs) {
5116
5153
  if (resolveCompetition() !== DEFAULT_COMPETITION) return true;
5117
5154
  return inLiveWindow(nowMs);
@@ -5125,17 +5162,23 @@ function liveAdapter() {
5125
5162
  }
5126
5163
  async function runRefresh(opts = {}) {
5127
5164
  const now = opts.now ?? /* @__PURE__ */ new Date();
5165
+ const nowMs = now.getTime();
5128
5166
  const source = opts.source ?? "espn";
5129
5167
  const competition = resolveCompetition();
5130
5168
  const cached = readState();
5131
- if (cached && cached.source === source && cached.competition === competition && ageMs(cached, now.getTime()) < MIN_REFRESH_MS) {
5132
- return;
5133
- }
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;
5134
5174
  if (!acquireLock()) return;
5135
5175
  try {
5136
- let live = [];
5137
- let degraded = false;
5138
- 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) {
5139
5182
  try {
5140
5183
  const r = await getLiveMatches(liveAdapter(), now);
5141
5184
  live = r.matches;
@@ -5143,8 +5186,27 @@ async function runRefresh(opts = {}) {
5143
5186
  } catch {
5144
5187
  degraded = true;
5145
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
+ }
5146
5200
  }
5147
- 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
+ });
5148
5210
  } finally {
5149
5211
  releaseLock();
5150
5212
  }
@@ -5154,6 +5216,11 @@ function shouldRefresh(now = Date.now()) {
5154
5216
  if (isLockFresh(now)) return false;
5155
5217
  return ageMs(readState(), now) > LIVE_TTL_MS;
5156
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
+ }
5157
5224
  function spawnRefresh(source) {
5158
5225
  try {
5159
5226
  const entry = process.argv[1];
@@ -5654,7 +5721,9 @@ function cmdPrompt({ cfg }) {
5654
5721
  const state = readCurrentState(cfg.source, resolveCompetition());
5655
5722
  const scoreLine = renderPrompt(state, { team, compact, max, flags: flagsEnabled() });
5656
5723
  out(renderPromptOutput(scoreLine, payload));
5657
- if (!state || shouldRefresh()) spawnRefresh(cfg.source);
5724
+ if (!state || shouldRefresh() || shouldRefreshFixtures(Date.now(), state)) {
5725
+ spawnRefresh(cfg.source);
5726
+ }
5658
5727
  } catch {
5659
5728
  out("");
5660
5729
  }
@@ -6234,7 +6303,7 @@ function handlePipeError(stream) {
6234
6303
  }
6235
6304
  handlePipeError(process.stdout);
6236
6305
  handlePipeError(process.stderr);
6237
- var VERSION = "0.8.7";
6306
+ var VERSION = "0.8.8";
6238
6307
  var DISCLAIMER = "Claudinho is an independent fan project. Not affiliated with or endorsed by FIFA or Anthropic.";
6239
6308
  function ctxFrom(cmd) {
6240
6309
  let root = cmd;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudinho/cli",
3
- "version": "0.8.7",
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.7"
64
+ "@claudinho/core": "0.8.8"
65
65
  },
66
66
  "scripts": {
67
67
  "build": "tsup",