@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.
Files changed (3) hide show
  1. package/README.md +33 -14
  2. package/dist/index.js +53 -28
  3. 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 USA --tz America/Los_Angeles`.
124
- REGENERATE immediately before merging — the market block is gate-conditional
125
- and the numbers drift. Never hand-edit. -->
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 United States
140
+ Next up for Mexico
128
141
 
129
- 🇺🇸 United States vs Paraguay 🇵🇾
130
- Jun 12 · 18:00 America/Los_Angeles
131
- SoFi Stadium, Inglewood, California, USA
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 USA
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
- const candidate = fixturesByTeam(code).find((m) => {
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: false };
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 eventSlug = entry?.eventSlug ?? deriveEventSlug(match);
4158
- if (!eventSlug) return { checked: true };
4169
+ const slugs = entry?.eventSlug ? [entry.eventSlug] : deriveEventSlugs(match);
4170
+ if (slugs.length === 0) return { checked: true };
4159
4171
  try {
4160
- const event = await this.fetchEvent(eventSlug, options?.timeoutMs);
4161
- const signal = event ? this.toSignal(match, eventSlug, event, options) : void 0;
4162
- return { signal, checked: true };
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 deriveEventSlug(match) {
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 void 0;
4242
- if (!/^[a-z]{3}$/.test(home) || !/^[a-z]{3}$/.test(away)) return void 0;
4243
- const date = match.kickoff.slice(0, 10);
4244
- if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return void 0;
4245
- return `fifwc-${home}-${away}-${date}`;
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) if (isReliableMarketSignal(s, { now })) out2.set(id, s);
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) if (marketDisplayable(s)) out2.set(id, s);
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.10";
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.10",
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.10"
64
+ "@claudinho/core": "0.8.12"
65
65
  },
66
66
  "scripts": {
67
67
  "build": "tsup",