@claudinho/cli 0.8.10 → 0.8.12
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/README.md +33 -14
- package/dist/index.js +53 -28
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
# @claudinho/cli ⚽
|
|
2
2
|
|
|
3
|
-
**The 2026 men's football tournament, right in your terminal.** Live scores, fixtures, group tables, and market signals — TZ-aware, localized, scriptable.
|
|
4
|
-
|
|
5
|
-
> ⚠️ **Not affiliated with, endorsed by, or connected to FIFA or Anthropic.**
|
|
6
|
-
> Claudinho is an independent, open-source fan project. It shows factual match
|
|
7
|
-
> data (scores, fixtures, standings) with emoji flags only — no logos, crests,
|
|
8
|
-
> kits, broadcast footage, or player likenesses.
|
|
3
|
+
**The 2026 men's football tournament, right in your terminal.** Live scores, fixtures, group tables, and market signals — TZ-aware, localized, scriptable. No API key, no signup.
|
|
9
4
|
|
|
10
5
|
## Install
|
|
11
6
|
|
|
@@ -15,6 +10,24 @@ npm i -g @claudinho/cli # installs the `claudinho` binary
|
|
|
15
10
|
npx @claudinho/cli today
|
|
16
11
|
```
|
|
17
12
|
|
|
13
|
+
`claudinho today` on a knockout night — penalty shootouts and all:
|
|
14
|
+
|
|
15
|
+
<!-- DEMO: verbatim `claudinho today <date>` from a knockout matchday. Shootouts render
|
|
16
|
+
as 1(3)–1(4). REGENERATE per matchday (capture after the day's games finish, so the
|
|
17
|
+
scores are live and current). Never hand-edit. -->
|
|
18
|
+
```text
|
|
19
|
+
Matches · 2026-06-29
|
|
20
|
+
|
|
21
|
+
🇧🇷 Brazil 2–1 Japan 🇯🇵 FT into the history books!
|
|
22
|
+
🇩🇪 Germany 1(3)–1(4) Paraguay 🇵🇾 FT it's all over!
|
|
23
|
+
🇳🇱 Netherlands 1(2)–1(3) Morocco 🇲🇦 FT the final whistle blows!
|
|
24
|
+
|
|
25
|
+
Live data: ESPN
|
|
26
|
+
Not affiliated with FIFA or Anthropic.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
All 104 fixtures ship bundled, so the schedule works offline; only live scores hit the network.
|
|
30
|
+
|
|
18
31
|
## Commands
|
|
19
32
|
|
|
20
33
|
```bash
|
|
@@ -120,18 +133,20 @@ claudinho share 760415 # one match by id
|
|
|
120
133
|
claudinho share next MEX --copy # …and copy it straight to the clipboard
|
|
121
134
|
```
|
|
122
135
|
|
|
123
|
-
<!-- DEMO CARD: verbatim output of `claudinho share next
|
|
124
|
-
REGENERATE
|
|
125
|
-
|
|
136
|
+
<!-- DEMO CARD: verbatim output of `claudinho share next MEX --tz America/Los_Angeles`.
|
|
137
|
+
REGENERATE before release — the matchup advances each round and any market block
|
|
138
|
+
drifts. Never hand-edit. -->
|
|
126
139
|
```text
|
|
127
|
-
Next up for
|
|
140
|
+
Next up for Mexico
|
|
128
141
|
|
|
129
|
-
|
|
130
|
-
Jun
|
|
131
|
-
|
|
142
|
+
🇲🇽 Mexico vs Ecuador 🇪🇨
|
|
143
|
+
Jun 30 · 18:00 America/Los_Angeles
|
|
144
|
+
Estadio Banorte, Mexico City, Mexico
|
|
145
|
+
Round of 32
|
|
132
146
|
|
|
147
|
+
Live data: ESPN
|
|
133
148
|
#VibingLaVidaLoca · Independent fan project · not affiliated with FIFA or Anthropic.
|
|
134
|
-
Try it: npx @claudinho/cli next
|
|
149
|
+
Try it: npx @claudinho/cli next MEX
|
|
135
150
|
```
|
|
136
151
|
|
|
137
152
|
Snippets are **plain text** (no color codes — they paste cleanly everywhere) and
|
|
@@ -225,6 +240,10 @@ from Polymarket; provider attribution and rate limits are respected.
|
|
|
225
240
|
|
|
226
241
|
MIT © 2026 Arturo Garrido · [source & issues](https://github.com/arturogarrido/claudinho)
|
|
227
242
|
|
|
243
|
+
> **Not affiliated with, endorsed by, or connected to FIFA or Anthropic.** An independent,
|
|
244
|
+
> open-source fan project showing factual match data (scores, fixtures, standings) with emoji
|
|
245
|
+
> flags only — no logos, crests, kits, broadcast footage, or player likenesses.
|
|
246
|
+
|
|
228
247
|
---
|
|
229
248
|
|
|
230
249
|
_Built while watching the games._ **#VibingLaVidaLoca** ⚽
|
package/dist/index.js
CHANGED
|
@@ -496,6 +496,10 @@ function localDate(iso, tz) {
|
|
|
496
496
|
timeZone: zone
|
|
497
497
|
}).format(new Date(iso));
|
|
498
498
|
}
|
|
499
|
+
function shiftUtcDate(dateISO, days) {
|
|
500
|
+
const [y, m, d] = dateISO.slice(0, 10).split("-").map(Number);
|
|
501
|
+
return new Date(Date.UTC(y ?? 1970, (m ?? 1) - 1, (d ?? 1) + days)).toISOString().slice(0, 10);
|
|
502
|
+
}
|
|
499
503
|
function outcomeFromScore(home, away) {
|
|
500
504
|
if (home > away) return "H";
|
|
501
505
|
if (home < away) return "A";
|
|
@@ -3835,14 +3839,19 @@ async function getBracket(adapter, opts = {}) {
|
|
|
3835
3839
|
source
|
|
3836
3840
|
};
|
|
3837
3841
|
}
|
|
3838
|
-
function shiftUtcDate(dateISO, days) {
|
|
3839
|
-
const [y, m, d] = dateISO.slice(0, 10).split("-").map(Number);
|
|
3840
|
-
return new Date(Date.UTC(y ?? 1970, (m ?? 1) - 1, (d ?? 1) + days)).toISOString().slice(0, 10);
|
|
3841
|
-
}
|
|
3842
3842
|
var EXTRA_TIME_SLACK_MS = 60 * 6e4;
|
|
3843
3843
|
async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Date()) {
|
|
3844
3844
|
const nowMs = now.getTime();
|
|
3845
|
-
|
|
3845
|
+
let fixtures = allFixtures();
|
|
3846
|
+
let overlayFailed = false;
|
|
3847
|
+
try {
|
|
3848
|
+
if (adapter.fetchWindow) {
|
|
3849
|
+
fixtures = mergeLive(fixtures, await adapter.fetchWindow(KNOCKOUT_WINDOW_START, KNOCKOUT_WINDOW_END));
|
|
3850
|
+
}
|
|
3851
|
+
} catch {
|
|
3852
|
+
overlayFailed = true;
|
|
3853
|
+
}
|
|
3854
|
+
const candidate = fixturesByTeam(code, fixtures).find((m) => {
|
|
3846
3855
|
const k = Date.parse(m.kickoff);
|
|
3847
3856
|
return nowMs >= k && nowMs <= k + LIVE_WINDOW_MS + EXTRA_TIME_SLACK_MS;
|
|
3848
3857
|
});
|
|
@@ -3851,8 +3860,8 @@ async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Dat
|
|
|
3851
3860
|
const m = r.match ?? candidate;
|
|
3852
3861
|
if (!isFinished(m.status)) return { ...r, match: m };
|
|
3853
3862
|
}
|
|
3854
|
-
const next = nextFixtureForTeam(code, { from: now });
|
|
3855
|
-
return { match: next, degraded:
|
|
3863
|
+
const next = nextFixtureForTeam(code, { from: now, fixtures });
|
|
3864
|
+
return { match: next, degraded: overlayFailed };
|
|
3856
3865
|
}
|
|
3857
3866
|
async function getNextFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Date()) {
|
|
3858
3867
|
const base = allFixtures();
|
|
@@ -3959,6 +3968,9 @@ function mapsCleanly(match, outcomes) {
|
|
|
3959
3968
|
if (match.stage === "GROUP" && !draw) return false;
|
|
3960
3969
|
return true;
|
|
3961
3970
|
}
|
|
3971
|
+
function marketSignalRendersFor(match, signal) {
|
|
3972
|
+
return signal.matchId === match.id && mapsCleanly(match, signal.outcomes);
|
|
3973
|
+
}
|
|
3962
3974
|
function hasSaneDistribution(outcomes) {
|
|
3963
3975
|
const priced = outcomes.filter((o) => Number.isFinite(o.probability) && o.probability > 0);
|
|
3964
3976
|
if (priced.length < 2) return false;
|
|
@@ -4154,12 +4166,15 @@ var PolymarketProvider = class {
|
|
|
4154
4166
|
*/
|
|
4155
4167
|
async resolveOne(match, options) {
|
|
4156
4168
|
const entry = (this.opts.mapping ?? BUNDLED_MAPPING)[match.id];
|
|
4157
|
-
const
|
|
4158
|
-
if (
|
|
4169
|
+
const slugs = entry?.eventSlug ? [entry.eventSlug] : deriveEventSlugs(match);
|
|
4170
|
+
if (slugs.length === 0) return { checked: true };
|
|
4159
4171
|
try {
|
|
4160
|
-
const
|
|
4161
|
-
|
|
4162
|
-
|
|
4172
|
+
for (const slug of slugs) {
|
|
4173
|
+
const event = await this.fetchEvent(slug, options?.timeoutMs);
|
|
4174
|
+
const signal = event ? this.toSignal(match, slug, event, options) : void 0;
|
|
4175
|
+
if (signal) return { signal, checked: true };
|
|
4176
|
+
}
|
|
4177
|
+
return { checked: true };
|
|
4163
4178
|
} catch {
|
|
4164
4179
|
return { checked: false };
|
|
4165
4180
|
}
|
|
@@ -4235,14 +4250,17 @@ var PolymarketProvider = class {
|
|
|
4235
4250
|
return signal.ambiguous ? void 0 : signal;
|
|
4236
4251
|
}
|
|
4237
4252
|
};
|
|
4238
|
-
function
|
|
4253
|
+
function deriveEventSlugs(match) {
|
|
4239
4254
|
const home = match.home.code.toLowerCase();
|
|
4240
4255
|
const away = match.away.code.toLowerCase();
|
|
4241
|
-
if (home === away || home === "tbd" || away === "tbd") return
|
|
4242
|
-
if (!/^[a-z]{3}$/.test(home) || !/^[a-z]{3}$/.test(away)) return
|
|
4243
|
-
const
|
|
4244
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(
|
|
4245
|
-
return
|
|
4256
|
+
if (home === away || home === "tbd" || away === "tbd") return [];
|
|
4257
|
+
if (!/^[a-z]{3}$/.test(home) || !/^[a-z]{3}$/.test(away)) return [];
|
|
4258
|
+
const utcDate = match.kickoff.slice(0, 10);
|
|
4259
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(utcDate)) return [];
|
|
4260
|
+
return [
|
|
4261
|
+
`fifwc-${home}-${away}-${utcDate}`,
|
|
4262
|
+
`fifwc-${home}-${away}-${shiftUtcDate(utcDate, -1)}`
|
|
4263
|
+
];
|
|
4246
4264
|
}
|
|
4247
4265
|
function slugToken(m) {
|
|
4248
4266
|
return (m.slug ?? "").toLowerCase().split("-").pop() ?? "";
|
|
@@ -5472,7 +5490,10 @@ async function reliableMarketSignals(ctx, matches) {
|
|
|
5472
5490
|
if (relevant.length === 0) return /* @__PURE__ */ new Map();
|
|
5473
5491
|
const raw = await marketSignalsFor(ctx, relevant, DEFAULT_ON_MARKET_OPTS);
|
|
5474
5492
|
const out2 = /* @__PURE__ */ new Map();
|
|
5475
|
-
for (const [id, s] of raw)
|
|
5493
|
+
for (const [id, s] of raw) {
|
|
5494
|
+
const m = relevant.find((x) => x.id === id);
|
|
5495
|
+
if (m && isReliableMarketSignal(s, { now }) && marketSignalRendersFor(m, s)) out2.set(id, s);
|
|
5496
|
+
}
|
|
5476
5497
|
return out2;
|
|
5477
5498
|
}
|
|
5478
5499
|
async function reliableMarketSignalFor(ctx, match) {
|
|
@@ -5868,8 +5889,8 @@ async function cmdMatch(id, ctx) {
|
|
|
5868
5889
|
out(disclaimer(t2, c));
|
|
5869
5890
|
}
|
|
5870
5891
|
var MARKET_INFO = "Prediction-market data is informational only.";
|
|
5871
|
-
function marketDisplayable(sig) {
|
|
5872
|
-
return !sig.ambiguous && sig.favorite != null && hasSaneDistribution(sig.outcomes);
|
|
5892
|
+
function marketDisplayable(match, sig) {
|
|
5893
|
+
return marketSignalRendersFor(match, sig) && !sig.ambiguous && sig.favorite != null && hasSaneDistribution(sig.outcomes);
|
|
5873
5894
|
}
|
|
5874
5895
|
function marketHeaderLine(m, cfg) {
|
|
5875
5896
|
const when = formatDate(m.kickoff, { tz: cfg.tz, locale: cfg.lang });
|
|
@@ -5888,13 +5909,14 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
5888
5909
|
precheck(cfg, t2);
|
|
5889
5910
|
const code = resolveTeamArg(team, "Usage: claudinho markets next <team> (or set CLAUDINHO_TEAM)");
|
|
5890
5911
|
const now2 = ctx.now ?? /* @__PURE__ */ new Date();
|
|
5891
|
-
const { match: fixture } = await marketFixtureForTeam(adapterFor(ctx), code, now2);
|
|
5912
|
+
const { match: fixture, degraded } = await marketFixtureForTeam(adapterFor(ctx), code, now2);
|
|
5892
5913
|
const sig = fixture && marketRelevant(fixture, now2) ? (await marketSignalsFor(ctx, [fixture], MARKETS_CMD_OPTS)).get(fixture.id) : void 0;
|
|
5893
|
-
const shown = sig && marketDisplayable(sig) ? sig : void 0;
|
|
5914
|
+
const shown = fixture && sig && marketDisplayable(fixture, sig) ? sig : void 0;
|
|
5894
5915
|
if (cfg.json) {
|
|
5895
5916
|
emitJson({
|
|
5896
5917
|
team: code,
|
|
5897
5918
|
matchId: fixture?.id ?? null,
|
|
5919
|
+
degraded,
|
|
5898
5920
|
informationalOnly: true,
|
|
5899
5921
|
signal: shown ?? null
|
|
5900
5922
|
});
|
|
@@ -5903,7 +5925,7 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
5903
5925
|
const c2 = painterFor(cfg);
|
|
5904
5926
|
out();
|
|
5905
5927
|
if (!fixture) {
|
|
5906
|
-
out(c2.dim(" " + t2("next.none", { team: code })));
|
|
5928
|
+
out(c2.dim(" " + (degraded ? t2("live.degraded") : t2("next.none", { team: code }))));
|
|
5907
5929
|
} else {
|
|
5908
5930
|
out(header(marketHeaderLine(fixture, cfg), c2));
|
|
5909
5931
|
out();
|
|
@@ -5920,7 +5942,7 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
5920
5942
|
const now2 = ctx.now ?? /* @__PURE__ */ new Date();
|
|
5921
5943
|
const { match } = await getMatchById(adapterFor(ctx), target);
|
|
5922
5944
|
const sig = match && marketRelevant(match, now2) ? (await marketSignalsFor(ctx, [match], MARKETS_CMD_OPTS)).get(match.id) : void 0;
|
|
5923
|
-
const shown = sig && marketDisplayable(sig) ? sig : void 0;
|
|
5945
|
+
const shown = match && sig && marketDisplayable(match, sig) ? sig : void 0;
|
|
5924
5946
|
if (cfg.json) {
|
|
5925
5947
|
emitJson({ matchId: target, informationalOnly: true, signal: shown ?? null });
|
|
5926
5948
|
return;
|
|
@@ -5949,7 +5971,7 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
5949
5971
|
const relevant = todays.filter((m) => marketRelevant(m, now));
|
|
5950
5972
|
const signals = await marketSignalsFor(ctx, relevant, MARKETS_CMD_OPTS);
|
|
5951
5973
|
const rows = relevant.map((m) => ({ match: m, signal: signals.get(m.id) })).filter(
|
|
5952
|
-
(r) => !!r.signal && marketDisplayable(r.signal)
|
|
5974
|
+
(r) => !!r.signal && marketDisplayable(r.match, r.signal)
|
|
5953
5975
|
);
|
|
5954
5976
|
if (cfg.json) {
|
|
5955
5977
|
const marketSignals = {};
|
|
@@ -5979,7 +6001,10 @@ function pickShareStyle(v) {
|
|
|
5979
6001
|
async function reliableShareSignals(ctx, matches) {
|
|
5980
6002
|
const raw = await reliableMarketSignals(ctx, matches);
|
|
5981
6003
|
const out2 = /* @__PURE__ */ new Map();
|
|
5982
|
-
for (const [id, s] of raw)
|
|
6004
|
+
for (const [id, s] of raw) {
|
|
6005
|
+
const m = matches.find((x) => x.id === id);
|
|
6006
|
+
if (m && marketDisplayable(m, s)) out2.set(id, s);
|
|
6007
|
+
}
|
|
5983
6008
|
return out2;
|
|
5984
6009
|
}
|
|
5985
6010
|
function emitShare(ctx, e, copy) {
|
|
@@ -6319,7 +6344,7 @@ function handlePipeError(stream) {
|
|
|
6319
6344
|
}
|
|
6320
6345
|
handlePipeError(process.stdout);
|
|
6321
6346
|
handlePipeError(process.stderr);
|
|
6322
|
-
var VERSION = "0.8.
|
|
6347
|
+
var VERSION = "0.8.12";
|
|
6323
6348
|
var DISCLAIMER = "Claudinho is an independent fan project. Not affiliated with or endorsed by FIFA or Anthropic.";
|
|
6324
6349
|
function ctxFrom(cmd) {
|
|
6325
6350
|
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.12",
|
|
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.12"
|
|
65
65
|
},
|
|
66
66
|
"scripts": {
|
|
67
67
|
"build": "tsup",
|