@claudinho/cli 0.8.7 → 0.8.9

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 +92 -15
  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;
@@ -5055,6 +5081,8 @@ function renderPrompt(state, opts = {}) {
5055
5081
  const flags = opts.flags ?? true;
5056
5082
  const team = opts.team?.toUpperCase();
5057
5083
  const live = liveMatchesFromCache(state, nowMs);
5084
+ const cachedFixtures = Array.isArray(state?.fixtures) ? state.fixtures : [];
5085
+ const schedule = cachedFixtures.length ? mergeLive(allFixtures(), cachedFixtures) : void 0;
5058
5086
  if (team) {
5059
5087
  const mine = live.find((m) => m.home?.code === team || m.away?.code === team);
5060
5088
  if (mine) return `\u26BD ${matchSegment(mine, compact, flags)}`;
@@ -5068,17 +5096,18 @@ function renderPrompt(state, opts = {}) {
5068
5096
  }
5069
5097
  const cacheFresh = !!state && state.degraded !== true && ageMs(state, nowMs) < DISPLAY_STALE_MS;
5070
5098
  if (!cacheFresh) {
5071
- const win = fixturesInLiveWindow(nowMs).filter(
5099
+ const win = fixturesInLiveWindow(nowMs, schedule).filter(
5072
5100
  (m) => !team || m.home.code === team || m.away.code === team
5073
5101
  );
5074
5102
  const first = win[0];
5075
5103
  if (first) {
5076
5104
  const more = win.length - 1;
5077
- return `\u26BD ${teamTok(first.home, flags)} vs ${teamTok(first.away, flags)} live \xB7 syncing\u2026` + (more > 0 ? ` +${more}` : "");
5105
+ const matchup = isResolvedFixture(first) ? `${teamTok(first.home, flags)} vs ${teamTok(first.away, flags)} ` : "";
5106
+ return `\u26BD ${matchup}live \xB7 syncing\u2026` + (more > 0 ? ` +${more}` : "");
5078
5107
  }
5079
5108
  }
5080
- const next = team ? nextFixtureForTeam(team, { from: now }) : nextOverall(nowMs);
5081
- if (next) {
5109
+ const next = team ? nextFixtureForTeam(team, { from: now, fixtures: schedule }) : nextOverall(nowMs, schedule);
5110
+ if (next && isResolvedFixture(next)) {
5082
5111
  return `${teamTok(next.home, flags)} vs ${teamTok(next.away, flags)} in ${countdown(next.kickoff, now)}`;
5083
5112
  }
5084
5113
  return "\u26BD \u2014";
@@ -5112,6 +5141,20 @@ ${lines}`;
5112
5141
  // src/refresh.ts
5113
5142
  import { spawn } from "child_process";
5114
5143
  var MIN_REFRESH_MS = 12e3;
5144
+ var FIXTURES_TTL_MS = 15 * 6e4;
5145
+ var FIXTURES_EMPTY_TTL_MS = 6e4;
5146
+ function fixturesStale(state, now) {
5147
+ const ttl = (state?.fixtures?.length ?? 0) > 0 ? FIXTURES_TTL_MS : FIXTURES_EMPTY_TTL_MS;
5148
+ return fixturesAgeMs(state, now) > ttl;
5149
+ }
5150
+ function nextStaticUpcoming(nowMs) {
5151
+ return [...allFixtures()].sort(byKickoff).find((m) => Date.parse(m.kickoff) >= nowMs);
5152
+ }
5153
+ function inKnockoutPhase(nowMs) {
5154
+ if (resolveCompetition() !== DEFAULT_COMPETITION) return false;
5155
+ const next = nextStaticUpcoming(nowMs);
5156
+ return !!next && next.stage !== "GROUP" && next.stage !== "FRIENDLY";
5157
+ }
5115
5158
  function liveWindowActive(nowMs) {
5116
5159
  if (resolveCompetition() !== DEFAULT_COMPETITION) return true;
5117
5160
  return inLiveWindow(nowMs);
@@ -5125,17 +5168,23 @@ function liveAdapter() {
5125
5168
  }
5126
5169
  async function runRefresh(opts = {}) {
5127
5170
  const now = opts.now ?? /* @__PURE__ */ new Date();
5171
+ const nowMs = now.getTime();
5128
5172
  const source = opts.source ?? "espn";
5129
5173
  const competition = resolveCompetition();
5130
5174
  const cached = readState();
5131
- if (cached && cached.source === source && cached.competition === competition && ageMs(cached, now.getTime()) < MIN_REFRESH_MS) {
5132
- return;
5133
- }
5175
+ const sameScope = !!cached && cached.source === source && cached.competition === competition;
5176
+ const base = sameScope ? cached : void 0;
5177
+ const needLive = liveWindowActive(nowMs) && (!base || ageMs(base, nowMs) >= MIN_REFRESH_MS);
5178
+ const needFixtures = inKnockoutPhase(nowMs) && fixturesStale(base, nowMs);
5179
+ if (!needLive && !needFixtures) return;
5134
5180
  if (!acquireLock()) return;
5135
5181
  try {
5136
- let live = [];
5137
- let degraded = false;
5138
- if (liveWindowActive(now.getTime())) {
5182
+ let live = base?.live ?? [];
5183
+ let degraded = base?.degraded ?? false;
5184
+ let updatedAt = base?.updatedAt ?? now.toISOString();
5185
+ let fixtures = base?.fixtures;
5186
+ let fixturesUpdatedAt = base?.fixturesUpdatedAt;
5187
+ if (needLive) {
5139
5188
  try {
5140
5189
  const r = await getLiveMatches(liveAdapter(), now);
5141
5190
  live = r.matches;
@@ -5143,8 +5192,27 @@ async function runRefresh(opts = {}) {
5143
5192
  } catch {
5144
5193
  degraded = true;
5145
5194
  }
5195
+ updatedAt = now.toISOString();
5196
+ }
5197
+ if (needFixtures) {
5198
+ try {
5199
+ const r = await getKnockoutFixtures(liveAdapter(), now);
5200
+ if (!r.degraded) {
5201
+ fixtures = r.fixtures;
5202
+ fixturesUpdatedAt = now.toISOString();
5203
+ }
5204
+ } catch {
5205
+ }
5146
5206
  }
5147
- writeState({ updatedAt: now.toISOString(), live, degraded, source, competition });
5207
+ writeState({
5208
+ updatedAt,
5209
+ live,
5210
+ degraded,
5211
+ source,
5212
+ competition,
5213
+ ...fixtures ? { fixtures } : {},
5214
+ ...fixturesUpdatedAt ? { fixturesUpdatedAt } : {}
5215
+ });
5148
5216
  } finally {
5149
5217
  releaseLock();
5150
5218
  }
@@ -5154,6 +5222,11 @@ function shouldRefresh(now = Date.now()) {
5154
5222
  if (isLockFresh(now)) return false;
5155
5223
  return ageMs(readState(), now) > LIVE_TTL_MS;
5156
5224
  }
5225
+ function shouldRefreshFixtures(now = Date.now(), state = readState()) {
5226
+ if (!inKnockoutPhase(now)) return false;
5227
+ if (isLockFresh(now)) return false;
5228
+ return fixturesStale(state, now);
5229
+ }
5157
5230
  function spawnRefresh(source) {
5158
5231
  try {
5159
5232
  const entry = process.argv[1];
@@ -5654,7 +5727,9 @@ function cmdPrompt({ cfg }) {
5654
5727
  const state = readCurrentState(cfg.source, resolveCompetition());
5655
5728
  const scoreLine = renderPrompt(state, { team, compact, max, flags: flagsEnabled() });
5656
5729
  out(renderPromptOutput(scoreLine, payload));
5657
- if (!state || shouldRefresh()) spawnRefresh(cfg.source);
5730
+ if (!state || shouldRefresh() || shouldRefreshFixtures(Date.now(), state)) {
5731
+ spawnRefresh(cfg.source);
5732
+ }
5658
5733
  } catch {
5659
5734
  out("");
5660
5735
  }
@@ -5665,7 +5740,9 @@ function cmdHook({ cfg }) {
5665
5740
  const state = readCurrentState(cfg.source, resolveCompetition());
5666
5741
  const ctx = renderHook(state, { team, flags: flagsEnabled() });
5667
5742
  if (ctx) out(ctx);
5668
- if (!state || shouldRefresh()) spawnRefresh(cfg.source);
5743
+ if (!state || shouldRefresh() || shouldRefreshFixtures(Date.now(), state)) {
5744
+ spawnRefresh(cfg.source);
5745
+ }
5669
5746
  } catch {
5670
5747
  }
5671
5748
  }
@@ -6234,7 +6311,7 @@ function handlePipeError(stream) {
6234
6311
  }
6235
6312
  handlePipeError(process.stdout);
6236
6313
  handlePipeError(process.stderr);
6237
- var VERSION = "0.8.7";
6314
+ var VERSION = "0.8.9";
6238
6315
  var DISCLAIMER = "Claudinho is an independent fan project. Not affiliated with or endorsed by FIFA or Anthropic.";
6239
6316
  function ctxFrom(cmd) {
6240
6317
  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.9",
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.9"
65
65
  },
66
66
  "scripts": {
67
67
  "build": "tsup",