@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.
- package/dist/index.js +119 -18
- 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
|
|
5065
|
-
|
|
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
|
-
|
|
5116
|
-
|
|
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
|
-
|
|
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({
|
|
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,
|
|
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
|
|
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()
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
64
|
+
"@claudinho/core": "0.8.8"
|
|
65
65
|
},
|
|
66
66
|
"scripts": {
|
|
67
67
|
"build": "tsup",
|