@claudinho/cli 0.8.16 → 0.8.18

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 +2 -1
  2. package/dist/index.js +166 -17
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -35,10 +35,11 @@ All 104 fixtures ship bundled, so the schedule works offline; only live scores h
35
35
  ```bash
36
36
  claudinho today [date] # a day's fixtures in your timezone (default: today), live scores inline
37
37
  claudinho live # matches in play right now
38
- claudinho next [TEAM] # a team's next fixture + countdown (default: $CLAUDINHO_TEAM)
38
+ claudinho next [TEAM] # a team's next fixture + countdown — TEAM is a name OR code (Mexico | MEX | "DR Congo"); default $CLAUDINHO_TEAM
39
39
  claudinho table [GROUP] # live cumulative group standings (default: all groups)
40
40
  claudinho bracket [STAGE] # knockout bracket (R32, R16, QF, SF, 3P, F); --tree for ASCII tree
41
41
  claudinho match <id> # a single match's detail
42
+ claudinho team <query> # resolve a name/code to its FIFA code, flag, and group (e.g. team "DR Congo")
42
43
  claudinho markets [target] # prediction-market signals: today | <date> | <id> | next <TEAM>
43
44
  # (next prefers the team's IN-PLAY match while one is live)
44
45
  claudinho share [target] # copy-pasteable snippet: today | live | <date> | <id> | next <TEAM> | table <GROUP> | bracket [STAGE]
package/dist/index.js CHANGED
@@ -2808,6 +2808,60 @@ function groups(fixtures = SCHEDULE) {
2808
2808
  for (const m of fixtures) if (m.group) set.add(m.group);
2809
2809
  return [...set].sort();
2810
2810
  }
2811
+ function norm2(s) {
2812
+ return s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z]/g, "");
2813
+ }
2814
+ function allTeams(fixtures = allFixtures()) {
2815
+ const seen = /* @__PURE__ */ new Map();
2816
+ for (const m of fixtures) {
2817
+ if (m.stage !== "GROUP") continue;
2818
+ for (const t2 of [m.home, m.away]) {
2819
+ if (!/^[A-Z]{3}$/.test(t2.code) || t2.flag === "\u{1F3F3}\uFE0F") continue;
2820
+ if (!seen.has(t2.code)) seen.set(t2.code, { ...t2, group: m.group ?? void 0 });
2821
+ }
2822
+ }
2823
+ return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name));
2824
+ }
2825
+ var TEAM_ALIASES = {
2826
+ turkey: "TUR",
2827
+ holland: "NED",
2828
+ korea: "KOR",
2829
+ skorea: "KOR",
2830
+ korearepublic: "KOR",
2831
+ republicofkorea: "KOR",
2832
+ czechrepublic: "CZE",
2833
+ congo: "COD",
2834
+ drcongo: "COD",
2835
+ drc: "COD",
2836
+ democraticrepublicofcongo: "COD",
2837
+ democraticrepublicofthecongo: "COD",
2838
+ cotedivoire: "CIV",
2839
+ caboverde: "CPV",
2840
+ bosnia: "BIH",
2841
+ bosniaandherzegovina: "BIH",
2842
+ us: "USA",
2843
+ america: "USA",
2844
+ unitedstatesofamerica: "USA"
2845
+ };
2846
+ function lookupTeam(query, fixtures = allFixtures()) {
2847
+ const roster = allTeams(fixtures);
2848
+ const q = norm2(query);
2849
+ if (!q) return { query, team: null, matches: [] };
2850
+ const byCode = roster.find((t2) => norm2(t2.code) === q);
2851
+ if (byCode) return { query, team: byCode, matches: [byCode] };
2852
+ const byName = roster.find((t2) => norm2(t2.name) === q);
2853
+ if (byName) return { query, team: byName, matches: [byName] };
2854
+ const aliasCode = TEAM_ALIASES[q];
2855
+ if (aliasCode) {
2856
+ const t2 = roster.find((r) => r.code === aliasCode);
2857
+ if (t2) return { query, team: t2, matches: [t2] };
2858
+ }
2859
+ if (q.length < 3) return { query, team: null, matches: [] };
2860
+ const prefix = roster.filter((t2) => norm2(t2.name).startsWith(q));
2861
+ const substr = roster.filter((t2) => norm2(t2.name).includes(q) && !prefix.includes(t2));
2862
+ const matches = [...prefix, ...substr];
2863
+ return { query, team: matches.length === 1 ? matches[0] : null, matches };
2864
+ }
2811
2865
  function blankRow(team) {
2812
2866
  return {
2813
2867
  team,
@@ -4149,7 +4203,8 @@ var PolymarketProvider = class {
4149
4203
  opts;
4150
4204
  name = "polymarket";
4151
4205
  async findSignal(match, options) {
4152
- return (await this.resolveOne(match, options)).signal;
4206
+ const deadline = options?.deadlineMs != null ? Date.now() + options.deadlineMs : Number.POSITIVE_INFINITY;
4207
+ return (await this.resolveOne(match, options, deadline)).signal;
4153
4208
  }
4154
4209
  async findSignals(matches, options) {
4155
4210
  const signals = /* @__PURE__ */ new Map();
@@ -4157,7 +4212,7 @@ var PolymarketProvider = class {
4157
4212
  const deadline = options?.deadlineMs != null ? Date.now() + options.deadlineMs : Number.POSITIVE_INFINITY;
4158
4213
  for (const m of matches) {
4159
4214
  if (Date.now() >= deadline) break;
4160
- const r = await this.resolveOne(m, options);
4215
+ const r = await this.resolveOne(m, options, deadline);
4161
4216
  if (r.checked) checked.add(m.id);
4162
4217
  if (r.signal) signals.set(m.id, r.signal);
4163
4218
  }
@@ -4169,13 +4224,16 @@ var PolymarketProvider = class {
4169
4224
  * provider/network error — so transient failures are retried, not
4170
4225
  * negative-cached.
4171
4226
  */
4172
- async resolveOne(match, options) {
4227
+ async resolveOne(match, options, deadline = Number.POSITIVE_INFINITY) {
4173
4228
  const entry = (this.opts.mapping ?? BUNDLED_MAPPING)[match.id];
4174
4229
  const slugs = entry?.eventSlug ? [entry.eventSlug] : deriveEventSlugs(match);
4175
4230
  if (slugs.length === 0) return { checked: true };
4231
+ const configured = options?.timeoutMs ?? this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
4176
4232
  try {
4177
4233
  for (const slug of slugs) {
4178
- const event = await this.fetchEvent(slug, options?.timeoutMs);
4234
+ const remaining = deadline - Date.now();
4235
+ if (remaining <= 0) return { checked: false };
4236
+ const event = await this.fetchEvent(slug, Math.min(configured, remaining));
4179
4237
  const signal = event ? this.toSignal(match, slug, event, options) : void 0;
4180
4238
  if (signal) return { signal, checked: true };
4181
4239
  }
@@ -4255,6 +4313,27 @@ var PolymarketProvider = class {
4255
4313
  return signal.ambiguous ? void 0 : signal;
4256
4314
  }
4257
4315
  };
4316
+ var POLYMARKET_TOKEN = {
4317
+ SUI: "che",
4318
+ // Switzerland
4319
+ NED: "nld",
4320
+ // Netherlands
4321
+ URU: "ury",
4322
+ // Uruguay
4323
+ POR: "prt",
4324
+ // Portugal
4325
+ CRO: "hrv",
4326
+ // Croatia
4327
+ COD: "cdr",
4328
+ // DR Congo
4329
+ CPV: "cvi"
4330
+ // Cabo Verde
4331
+ };
4332
+ function pmTokens(code) {
4333
+ const c = code.toLowerCase();
4334
+ const alias = POLYMARKET_TOKEN[code.toUpperCase()];
4335
+ return alias && alias !== c ? [alias, c] : [c];
4336
+ }
4258
4337
  function deriveEventSlugs(match) {
4259
4338
  const home = match.home.code.toLowerCase();
4260
4339
  const away = match.away.code.toLowerCase();
@@ -4262,10 +4341,18 @@ function deriveEventSlugs(match) {
4262
4341
  if (!/^[a-z]{3}$/.test(home) || !/^[a-z]{3}$/.test(away)) return [];
4263
4342
  const utcDate = match.kickoff.slice(0, 10);
4264
4343
  if (!/^\d{4}-\d{2}-\d{2}$/.test(utcDate)) return [];
4265
- return [
4266
- `fifwc-${home}-${away}-${utcDate}`,
4267
- `fifwc-${home}-${away}-${shiftUtcDate(utcDate, -1)}`
4268
- ];
4344
+ const dates = [utcDate, shiftUtcDate(utcDate, -1)];
4345
+ const homeToks = pmTokens(match.home.code);
4346
+ const awayToks = pmTokens(match.away.code);
4347
+ const slugs = [];
4348
+ for (const d of dates) {
4349
+ for (const h of homeToks) {
4350
+ for (const a of awayToks) {
4351
+ slugs.push(`fifwc-${h}-${a}-${d}`);
4352
+ }
4353
+ }
4354
+ }
4355
+ return [...new Set(slugs)];
4269
4356
  }
4270
4357
  function slugToken(m) {
4271
4358
  return (m.slug ?? "").toLowerCase().split("-").pop() ?? "";
@@ -4274,10 +4361,10 @@ function isDrawMarket(m) {
4274
4361
  return slugToken(m) === "draw" || (m.groupItemTitle ?? "").trim().toLowerCase().startsWith("draw");
4275
4362
  }
4276
4363
  function pickMarket(markets, teamCode, teamName) {
4277
- const code = teamCode.toLowerCase();
4364
+ const tokens = pmTokens(teamCode);
4278
4365
  const name = teamName.trim().toLowerCase();
4279
4366
  const teamMarkets = markets.filter((m) => !isDrawMarket(m));
4280
- const bySlug = teamMarkets.find((m) => slugToken(m) === code);
4367
+ const bySlug = teamMarkets.find((m) => tokens.includes(slugToken(m)));
4281
4368
  if (bySlug) return bySlug;
4282
4369
  return teamMarkets.find((m) => (m.groupItemTitle ?? "").trim().toLowerCase() === name);
4283
4370
  }
@@ -4657,6 +4744,10 @@ var EN2 = {
4657
4744
  "next.none": "No upcoming fixture found for {team}.",
4658
4745
  "next.label": "Next up for {team}",
4659
4746
  "next.in": "in {countdown}",
4747
+ "team.group": "Group {group}",
4748
+ "team.ambiguous": '"{query}" is ambiguous. Did you mean:',
4749
+ "team.none": 'No team found for "{query}". Try a nation name or 3-letter code (e.g. Mexico, MEX).',
4750
+ "team.usage": "Usage: claudinho team <name or code> (e.g. claudinho team Mexico)",
4660
4751
  "table.title": "Group {group}",
4661
4752
  "table.none": "No group found for {group}.",
4662
4753
  "table.degraded": "Live standings unavailable \u2014 showing the group roster.",
@@ -4691,6 +4782,10 @@ var ES2 = {
4691
4782
  "next.none": "No se encontr\xF3 pr\xF3ximo partido para {team}.",
4692
4783
  "next.label": "Pr\xF3ximo partido de {team}",
4693
4784
  "next.in": "en {countdown}",
4785
+ "team.group": "Grupo {group}",
4786
+ "team.ambiguous": '"{query}" es ambiguo. \xBFQuisiste decir:',
4787
+ "team.none": 'No se encontr\xF3 ning\xFAn equipo para "{query}". Prueba un nombre de pa\xEDs o un c\xF3digo de 3 letras (p. ej. Mexico, MEX).',
4788
+ "team.usage": "Uso: claudinho team <nombre o c\xF3digo> (p. ej. claudinho team Mexico)",
4694
4789
  "table.title": "Grupo {group}",
4695
4790
  "table.none": "No se encontr\xF3 el grupo {group}.",
4696
4791
  "table.degraded": "Tabla en vivo no disponible \u2014 mostrando la lista del grupo.",
@@ -4725,6 +4820,10 @@ var PT2 = {
4725
4820
  "next.none": "Nenhum pr\xF3ximo jogo encontrado para {team}.",
4726
4821
  "next.label": "Pr\xF3ximo jogo de {team}",
4727
4822
  "next.in": "em {countdown}",
4823
+ "team.group": "Grupo {group}",
4824
+ "team.ambiguous": '"{query}" \xE9 amb\xEDguo. Voc\xEA quis dizer:',
4825
+ "team.none": 'Nenhuma sele\xE7\xE3o encontrada para "{query}". Tente um nome de pa\xEDs ou um c\xF3digo de 3 letras (ex. Mexico, MEX).',
4826
+ "team.usage": "Uso: claudinho team <nome ou c\xF3digo> (ex. claudinho team Mexico)",
4728
4827
  "table.title": "Grupo {group}",
4729
4828
  "table.none": "Grupo {group} n\xE3o encontrado.",
4730
4829
  "table.degraded": "Classifica\xE7\xE3o ao vivo indispon\xEDvel \u2014 mostrando os times do grupo.",
@@ -4759,6 +4858,10 @@ var FR2 = {
4759
4858
  "next.none": "Aucun prochain match trouv\xE9 pour {team}.",
4760
4859
  "next.label": "Prochain match de {team}",
4761
4860
  "next.in": "dans {countdown}",
4861
+ "team.group": "Groupe {group}",
4862
+ "team.ambiguous": '"{query}" est ambigu. Vouliez-vous dire :',
4863
+ "team.none": 'Aucune \xE9quipe trouv\xE9e pour "{query}". Essayez un nom de pays ou un code \xE0 3 lettres (p. ex. Mexico, MEX).',
4864
+ "team.usage": "Usage : claudinho team <nom ou code> (p. ex. claudinho team Mexico)",
4762
4865
  "table.title": "Groupe {group}",
4763
4866
  "table.none": "Groupe {group} introuvable.",
4764
4867
  "table.degraded": "Classement en direct indisponible \u2014 affichage de la composition du groupe.",
@@ -5563,9 +5666,17 @@ function precheck(cfg, t2, date) {
5563
5666
  }
5564
5667
  }
5565
5668
  function resolveTeamArg(team, usage) {
5566
- const code = team ?? process.env.CLAUDINHO_TEAM;
5567
- if (!code) throw new InputError(usage);
5568
- return code.toUpperCase();
5669
+ const raw = team ?? process.env.CLAUDINHO_TEAM;
5670
+ if (!raw) throw new InputError(usage);
5671
+ const { team: hit, matches } = lookupTeam(raw);
5672
+ if (hit) return hit.code;
5673
+ if (matches.length > 1) {
5674
+ throw new InputError(
5675
+ `"${raw}" is ambiguous \u2014 did you mean ${matches.map((m) => `${m.name} (${m.code})`).join(", ")}? Use the 3-letter code.`
5676
+ );
5677
+ }
5678
+ if (/^[A-Za-z]{3}$/.test(raw)) return raw.toUpperCase();
5679
+ throw new InputError(`No team found for "${raw}". Use a nation name or 3-letter code (e.g. Mexico, MEX).`);
5569
5680
  }
5570
5681
  async function cmdToday(date, ctx) {
5571
5682
  const { cfg, t: t2 } = ctx;
@@ -5671,6 +5782,37 @@ async function cmdNext(team, ctx) {
5671
5782
  out(disclaimer(t2, c));
5672
5783
  maybeStarNudge(ctx);
5673
5784
  }
5785
+ function cmdTeam(query, ctx) {
5786
+ const { cfg, t: t2 } = ctx;
5787
+ precheck(cfg, t2);
5788
+ const q = (query ?? "").trim();
5789
+ const { team, matches } = lookupTeam(q);
5790
+ if (cfg.json) {
5791
+ emitJson({ query: q, team: team ?? null, matches, count: matches.length });
5792
+ return;
5793
+ }
5794
+ const c = painterFor(cfg);
5795
+ const flags = flagsEnabled();
5796
+ const label = (tm) => {
5797
+ const flag = flags ? `${tm.flag} ` : "";
5798
+ const grp = tm.group ? ` \xB7 ${t2("team.group", { group: tm.group })}` : "";
5799
+ return ` ${flag}${c.bold(tm.name)} ${c.dim(tm.code + grp)}`;
5800
+ };
5801
+ out();
5802
+ if (!q) {
5803
+ out(" " + c.dim(t2("team.usage")));
5804
+ } else if (team) {
5805
+ out(label(team));
5806
+ } else if (matches.length > 0) {
5807
+ out(" " + c.dim(t2("team.ambiguous", { query: q })));
5808
+ for (const m of matches) out(label(m));
5809
+ } else {
5810
+ out(" " + c.dim(t2("team.none", { query: q })));
5811
+ }
5812
+ out();
5813
+ out(disclaimer(t2, c));
5814
+ maybeStarNudge(ctx);
5815
+ }
5674
5816
  async function cmdTable(group, ctx) {
5675
5817
  const { cfg, t: t2 } = ctx;
5676
5818
  precheck(cfg, t2);
@@ -6423,7 +6565,7 @@ function handlePipeError(stream) {
6423
6565
  }
6424
6566
  handlePipeError(process.stdout);
6425
6567
  handlePipeError(process.stderr);
6426
- var VERSION = "0.8.16";
6568
+ var VERSION = "0.8.18";
6427
6569
  var DISCLAIMER = "Claudinho is an independent fan project. Not affiliated with or endorsed by FIFA or Anthropic.";
6428
6570
  function ctxFrom(cmd) {
6429
6571
  let root = cmd;
@@ -6457,13 +6599,20 @@ program.command("live").description("show matches in play right now").action(asy
6457
6599
  fail(e);
6458
6600
  }
6459
6601
  });
6460
- program.command("next").description("show a team's next fixture").argument("[team]", "team code, e.g. MEX (default: $CLAUDINHO_TEAM)").action(async (team, _opts, cmd) => {
6602
+ program.command("next").description("show a team's next fixture").argument("[team]", "team name or code, e.g. Mexico or MEX (default: $CLAUDINHO_TEAM)").action(async (team, _opts, cmd) => {
6461
6603
  try {
6462
6604
  await cmdNext(team, ctxFrom(cmd));
6463
6605
  } catch (e) {
6464
6606
  fail(e);
6465
6607
  }
6466
6608
  });
6609
+ program.command("team").description("resolve a nation name or code to its FIFA code, flag, and group").argument("<query>", 'team name or 3-letter code, e.g. Mexico, MEX, "DR Congo"').action((query, _opts, cmd) => {
6610
+ try {
6611
+ cmdTeam(query, ctxFrom(cmd));
6612
+ } catch (e) {
6613
+ fail(e);
6614
+ }
6615
+ });
6467
6616
  program.command("table").description("show group standings (default: all groups)").argument("[group]", "group letter A-L").action(async (group, _opts, cmd) => {
6468
6617
  try {
6469
6618
  await cmdTable(group, ctxFrom(cmd));
@@ -6485,7 +6634,7 @@ program.command("match").description("show a single match by id").argument("<id>
6485
6634
  fail(e);
6486
6635
  }
6487
6636
  });
6488
- program.command("markets").description("show prediction-market signals (read-only, informational only)").argument("[target]", 'date (YYYY-MM-DD), match id, "today", or "next"').argument("[team]", 'team code when target is "next" (default: $CLAUDINHO_TEAM)').action(async (target, team, _opts, cmd) => {
6637
+ program.command("markets").description("show prediction-market signals (read-only, informational only)").argument("[target]", 'date (YYYY-MM-DD), match id, "today", or "next"').argument("[team]", 'team name or code when target is "next", e.g. Mexico or MEX (default: $CLAUDINHO_TEAM)').action(async (target, team, _opts, cmd) => {
6489
6638
  try {
6490
6639
  await cmdMarkets(target, team, ctxFrom(cmd));
6491
6640
  } catch (e) {
@@ -6494,7 +6643,7 @@ program.command("markets").description("show prediction-market signals (read-onl
6494
6643
  });
6495
6644
  program.command("share").description("print a shareable, copy-pasteable match snippet (#VibingLaVidaLoca)").argument("[target]", '"today" (default), "live", a date, a match id, "next", "table", or "bracket"').argument(
6496
6645
  "[team]",
6497
- 'team code for "next" (default: $CLAUDINHO_TEAM), group letter for "table", or stage for "bracket"'
6646
+ 'team name or code for "next" (default: $CLAUDINHO_TEAM), group letter for "table", or stage for "bracket"'
6498
6647
  ).option("--style <style>", "snippet style: social (default) or compact").option("--copy", "also copy the snippet to the clipboard (best-effort)").option("--no-hashtag", "omit the #VibingLaVidaLoca tag").option("--no-install-line", "omit the install/run cue").action(async (target, team, opts, cmd) => {
6499
6648
  try {
6500
6649
  await cmdShare(target, team, opts, ctxFrom(cmd));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudinho/cli",
3
- "version": "0.8.16",
3
+ "version": "0.8.18",
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.16"
64
+ "@claudinho/core": "0.8.18"
65
65
  },
66
66
  "scripts": {
67
67
  "build": "tsup",