@claudinho/cli 0.4.3 → 0.5.1

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 +3 -2
  2. package/dist/index.js +223 -44
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -21,11 +21,11 @@ npx @claudinho/cli today
21
21
  claudinho today [date] # a day's fixtures in your timezone (default: today), live scores inline
22
22
  claudinho live # matches in play right now
23
23
  claudinho next [TEAM] # a team's next fixture + countdown (default: $CLAUDINHO_TEAM)
24
- claudinho table [GROUP] # group standings (default: all groups)
24
+ claudinho table [GROUP] # live cumulative group standings (default: all groups)
25
25
  claudinho match <id> # a single match's detail
26
26
  claudinho markets [target] # prediction-market signals: today | <date> | <id> | next <TEAM>
27
27
  # (next prefers the team's IN-PLAY match while one is live)
28
- claudinho share [target] # copy-pasteable match snippet: today | live | <date> | <id> | next <TEAM>
28
+ claudinho share [target] # copy-pasteable snippet: today | live | <date> | <id> | next <TEAM> | table <GROUP>
29
29
  claudinho prompt # one compact status line (for statusline/tmux/Starship)
30
30
  claudinho init-statusline # wire it into the Claude Code statusline
31
31
  claudinho hook # live-score context for a Claude Code hook (silent off-match)
@@ -108,6 +108,7 @@ social posts, READMEs, and issue comments — your terminal football, ready to p
108
108
  claudinho share # today's matches
109
109
  claudinho share live # matches in play
110
110
  claudinho share next MEX # a team's next fixture (+ market read, when reliable)
111
+ claudinho share table A # a group's standings card (facts only, no market line)
111
112
  claudinho share 760415 # one match by id
112
113
  claudinho share next MEX --copy # …and copy it straight to the clipboard
113
114
  ```
package/dist/index.js CHANGED
@@ -2762,6 +2762,43 @@ function mapEspnEvent(ev, ctx = {}) {
2762
2762
  function toEspnDate(d) {
2763
2763
  return d.replace(/\D/g, "").slice(0, 8);
2764
2764
  }
2765
+ function statVal(stats, name) {
2766
+ const v = stats?.find((s) => s.name === name)?.value;
2767
+ return typeof v === "number" && Number.isFinite(v) ? Math.round(v) : 0;
2768
+ }
2769
+ function entryToRow(e) {
2770
+ return {
2771
+ team: toTeam(e.team),
2772
+ played: statVal(e.stats, "gamesPlayed"),
2773
+ won: statVal(e.stats, "wins"),
2774
+ drawn: statVal(e.stats, "ties"),
2775
+ lost: statVal(e.stats, "losses"),
2776
+ goalsFor: statVal(e.stats, "pointsFor"),
2777
+ goalsAgainst: statVal(e.stats, "pointsAgainst"),
2778
+ goalDiff: statVal(e.stats, "pointDifferential"),
2779
+ points: statVal(e.stats, "points")
2780
+ };
2781
+ }
2782
+ function parseStandings(data) {
2783
+ const out2 = [];
2784
+ for (const child of data.children ?? []) {
2785
+ const letter = (child.name ?? child.abbreviation ?? "").match(/Group\s+([A-L])/i)?.[1]?.toUpperCase();
2786
+ if (!letter) continue;
2787
+ const ranked = (child.standings?.entries ?? []).map((e) => ({
2788
+ row: entryToRow(e),
2789
+ rank: statVal(e.stats, "rank")
2790
+ }));
2791
+ ranked.sort((a, b) => {
2792
+ if (a.rank && b.rank && a.rank !== b.rank) return a.rank - b.rank;
2793
+ const r = a.row;
2794
+ const s = b.row;
2795
+ return s.points - r.points || s.goalDiff - r.goalDiff || s.goalsFor - r.goalsFor || r.team.code.localeCompare(s.team.code);
2796
+ });
2797
+ out2.push({ group: letter, rows: ranked.map((x) => x.row) });
2798
+ }
2799
+ out2.sort((a, b) => a.group.localeCompare(b.group));
2800
+ return out2;
2801
+ }
2765
2802
  var EspnAdapter = class {
2766
2803
  constructor(opts = {}) {
2767
2804
  this.opts = opts;
@@ -2781,25 +2818,30 @@ var EspnAdapter = class {
2781
2818
  const today = await this.fetchScoreboard();
2782
2819
  return today.filter((m) => m.status === "LIVE" || m.status === "HT");
2783
2820
  }
2821
+ /** Standings endpoint URL (lives under apis/v2, not site/v2; derived from base). */
2822
+ standingsUrl() {
2823
+ const base = this.opts.baseUrl ?? DEFAULT_BASE;
2824
+ return `${base.replace("/apis/site/v2/", "/apis/v2/")}/standings`;
2825
+ }
2826
+ /**
2827
+ * Authoritative, cumulative group tables from the standings endpoint. Throws
2828
+ * on fetch/parse failure (the caller decides the fallback). Group-stage only:
2829
+ * non-group `children` are filtered out by {@link parseStandings}.
2830
+ */
2831
+ async fetchStandings() {
2832
+ return parseStandings(await this.get(this.standingsUrl()));
2833
+ }
2784
2834
  /**
2785
2835
  * Build (and cache) a team-code -> group-letter map from the standings
2786
- * endpoint. Best-effort: returns {} if standings are unavailable.
2836
+ * endpoint. Best-effort: returns {} if standings are unavailable. Reuses the
2837
+ * same parse as {@link fetchStandings}, so the two never drift.
2787
2838
  */
2788
2839
  async fetchGroupMap(force = false) {
2789
2840
  if (this.groupMap && !force) return this.groupMap;
2790
- const base = this.opts.baseUrl ?? DEFAULT_BASE;
2791
- const standingsUrl = `${base.replace("/apis/site/v2/", "/apis/v2/")}/standings`;
2792
2841
  const map = {};
2793
2842
  try {
2794
- const data = await this.get(standingsUrl);
2795
- for (const child of data.children ?? []) {
2796
- const letter = (child.name ?? child.abbreviation ?? "").match(/Group\s+([A-L])/i)?.[1]?.toUpperCase();
2797
- if (!letter) continue;
2798
- for (const e of child.standings?.entries ?? []) {
2799
- const code = e.team?.abbreviation?.toUpperCase();
2800
- if (code) map[code] = letter;
2801
- }
2802
- }
2843
+ const tables = parseStandings(await this.get(this.standingsUrl()));
2844
+ for (const t of tables) for (const r of t.rows) map[r.team.code] = t.group;
2803
2845
  } catch {
2804
2846
  }
2805
2847
  this.groupMap = map;
@@ -2870,6 +2912,22 @@ async function getMatchesForDate(adapter, dateISO) {
2870
2912
  return { matches: base, degraded: true };
2871
2913
  }
2872
2914
  }
2915
+ async function getStandings(adapter, group) {
2916
+ const want = group?.toUpperCase();
2917
+ if (adapter.fetchStandings) {
2918
+ try {
2919
+ const all = await adapter.fetchStandings();
2920
+ const tables2 = (want ? all.filter((t) => t.group === want) : all).sort(
2921
+ (a, b) => a.group.localeCompare(b.group)
2922
+ );
2923
+ return { tables: tables2, degraded: false, source: adapter.name };
2924
+ } catch {
2925
+ }
2926
+ }
2927
+ const letters = want ? [want] : groups();
2928
+ const tables = letters.map((g) => ({ group: g, rows: computeStandings(fixturesByGroup(g)) })).filter((t) => t.rows.length > 0);
2929
+ return { tables, degraded: true };
2930
+ }
2873
2931
  function shiftUtcDate(dateISO, days) {
2874
2932
  const [y, m, d] = dateISO.slice(0, 10).split("-").map(Number);
2875
2933
  return new Date(Date.UTC(y ?? 1970, (m ?? 1) - 1, (d ?? 1) + days)).toISOString().slice(0, 10);
@@ -3402,11 +3460,53 @@ function formatShareSnippet(input, options = {}) {
3402
3460
  blocks.push(card.join("\n"));
3403
3461
  }
3404
3462
  }
3463
+ if (input.degraded && input.matches.length > 0) {
3464
+ blocks.push("(Live data unavailable \u2014 showing the bundled schedule, not live scores.)");
3465
+ }
3466
+ blocks.push(
3467
+ shareFooter({
3468
+ source: input.source,
3469
+ installLine: input.installLine,
3470
+ includeHashtag,
3471
+ includeInstall
3472
+ })
3473
+ );
3474
+ return blocks.join("\n\n");
3475
+ }
3476
+ function shareFooter(opts) {
3405
3477
  const footer = [];
3406
- if (input.source) footer.push(`Live data: ${liveSourceLabel(input.source)}`);
3407
- footer.push([includeHashtag ? SHARE_HASHTAG : "", SHARE_DISCLAIMER].filter(Boolean).join(" \xB7 "));
3408
- if (includeInstall && input.installLine) footer.push(`Try it: ${input.installLine}`);
3409
- blocks.push(footer.join("\n"));
3478
+ if (opts.source) footer.push(`Live data: ${liveSourceLabel(opts.source)}`);
3479
+ footer.push(
3480
+ [opts.includeHashtag ? SHARE_HASHTAG : "", SHARE_DISCLAIMER].filter(Boolean).join(" \xB7 ")
3481
+ );
3482
+ if (opts.includeInstall && opts.installLine) footer.push(`Try it: ${opts.installLine}`);
3483
+ return footer.join("\n");
3484
+ }
3485
+ function gd(n) {
3486
+ return n > 0 ? `+${n}` : `${n}`;
3487
+ }
3488
+ function tableRow(r, rank) {
3489
+ return `${rank}. ${r.team.flag} ${r.team.code} ${r.points} pts \xB7 ${r.won}-${r.drawn}-${r.lost} \xB7 ${gd(r.goalDiff)}`;
3490
+ }
3491
+ function formatShareTable(input, options = {}) {
3492
+ const includeHashtag = options.includeHashtag !== false;
3493
+ const includeInstall = options.includeInstallLine !== false;
3494
+ const blocks = [];
3495
+ if (input.tables.length === 0) {
3496
+ blocks.push(input.emptyNote ?? "No standings available.");
3497
+ } else {
3498
+ for (const { group, rows } of input.tables) {
3499
+ blocks.push(
3500
+ [`Group ${group} \xB7 standings`, "", ...rows.map((r, i) => tableRow(r, i + 1))].join("\n")
3501
+ );
3502
+ }
3503
+ if (input.degraded) {
3504
+ blocks.push("(Live standings unavailable \u2014 group roster, not live results.)");
3505
+ }
3506
+ }
3507
+ blocks.push(
3508
+ shareFooter({ source: input.source, installLine: input.installLine, includeHashtag, includeInstall })
3509
+ );
3410
3510
  return blocks.join("\n\n");
3411
3511
  }
3412
3512
 
@@ -3460,11 +3560,15 @@ var EN = {
3460
3560
  "today.none": "No matches scheduled for this date.",
3461
3561
  "live.title": "Live now",
3462
3562
  "live.none": "No matches in play right now.",
3563
+ "live.degraded": "Live scores unavailable right now \u2014 couldn't reach the data provider.",
3564
+ "feed.degraded": "Live scores unavailable \u2014 showing the bundled schedule.",
3463
3565
  "next.none": "No upcoming fixture found for {team}.",
3464
3566
  "next.label": "Next up for {team}",
3465
3567
  "next.in": "in {countdown}",
3466
3568
  "table.title": "Group {group}",
3467
3569
  "table.none": "No group found for {group}.",
3570
+ "table.degraded": "Live standings unavailable \u2014 showing the group roster.",
3571
+ "table.empty": "No standings available.",
3468
3572
  "match.none": "No match found with id {id}.",
3469
3573
  "status.scheduled": "scheduled",
3470
3574
  "status.live": "LIVE",
@@ -3490,11 +3594,15 @@ var ES = {
3490
3594
  "today.none": "No hay partidos para esta fecha.",
3491
3595
  "live.title": "En vivo",
3492
3596
  "live.none": "No hay partidos en juego ahora mismo.",
3597
+ "live.degraded": "Marcadores en vivo no disponibles \u2014 no se pudo conectar con el proveedor de datos.",
3598
+ "feed.degraded": "Marcadores en vivo no disponibles \u2014 mostrando el calendario.",
3493
3599
  "next.none": "No se encontr\xF3 pr\xF3ximo partido para {team}.",
3494
3600
  "next.label": "Pr\xF3ximo partido de {team}",
3495
3601
  "next.in": "en {countdown}",
3496
3602
  "table.title": "Grupo {group}",
3497
3603
  "table.none": "No se encontr\xF3 el grupo {group}.",
3604
+ "table.degraded": "Tabla en vivo no disponible \u2014 mostrando la lista del grupo.",
3605
+ "table.empty": "No hay clasificaci\xF3n disponible.",
3498
3606
  "match.none": "No se encontr\xF3 partido con id {id}.",
3499
3607
  "status.scheduled": "programado",
3500
3608
  "status.live": "EN VIVO",
@@ -3520,11 +3628,15 @@ var PT = {
3520
3628
  "today.none": "Nenhum jogo para esta data.",
3521
3629
  "live.title": "Ao vivo",
3522
3630
  "live.none": "Nenhum jogo em andamento agora.",
3631
+ "live.degraded": "Placar ao vivo indispon\xEDvel \u2014 n\xE3o foi poss\xEDvel conectar ao provedor de dados.",
3632
+ "feed.degraded": "Placar ao vivo indispon\xEDvel \u2014 mostrando a tabela de jogos.",
3523
3633
  "next.none": "Nenhum pr\xF3ximo jogo encontrado para {team}.",
3524
3634
  "next.label": "Pr\xF3ximo jogo de {team}",
3525
3635
  "next.in": "em {countdown}",
3526
3636
  "table.title": "Grupo {group}",
3527
3637
  "table.none": "Grupo {group} n\xE3o encontrado.",
3638
+ "table.degraded": "Classifica\xE7\xE3o ao vivo indispon\xEDvel \u2014 mostrando os times do grupo.",
3639
+ "table.empty": "Classifica\xE7\xE3o indispon\xEDvel.",
3528
3640
  "match.none": "Nenhum jogo encontrado com id {id}.",
3529
3641
  "status.scheduled": "agendado",
3530
3642
  "status.live": "AO VIVO",
@@ -3550,11 +3662,15 @@ var FR = {
3550
3662
  "today.none": "Aucun match pr\xE9vu pour cette date.",
3551
3663
  "live.title": "En direct",
3552
3664
  "live.none": "Aucun match en cours pour l'instant.",
3665
+ "live.degraded": "Scores en direct indisponibles \u2014 impossible de joindre le fournisseur de donn\xE9es.",
3666
+ "feed.degraded": "Scores en direct indisponibles \u2014 affichage du calendrier.",
3553
3667
  "next.none": "Aucun prochain match trouv\xE9 pour {team}.",
3554
3668
  "next.label": "Prochain match de {team}",
3555
3669
  "next.in": "dans {countdown}",
3556
3670
  "table.title": "Groupe {group}",
3557
3671
  "table.none": "Groupe {group} introuvable.",
3672
+ "table.degraded": "Classement en direct indisponible \u2014 affichage de la composition du groupe.",
3673
+ "table.empty": "Aucun classement disponible.",
3558
3674
  "match.none": "Aucun match trouv\xE9 avec id {id}.",
3559
3675
  "status.scheduled": "pr\xE9vu",
3560
3676
  "status.live": "DIRECT",
@@ -4191,6 +4307,7 @@ async function cmdToday(date, ctx) {
4191
4307
  }
4192
4308
  }
4193
4309
  out();
4310
+ if (degraded) out(c.dim(" " + t("feed.degraded")));
4194
4311
  const src = dataSource(source, c);
4195
4312
  if (src) out(src);
4196
4313
  out(disclaimer(t, c));
@@ -4208,7 +4325,9 @@ async function cmdLive(ctx) {
4208
4325
  out();
4209
4326
  out(header(t("live.title"), c));
4210
4327
  out();
4211
- if (matches.length === 0) {
4328
+ if (degraded) {
4329
+ out(c.dim(" " + t("live.degraded")));
4330
+ } else if (matches.length === 0) {
4212
4331
  out(c.dim(" " + t("live.none")));
4213
4332
  } else {
4214
4333
  for (const m of matches) out(matchLine(m, cfg, t, c));
@@ -4218,10 +4337,10 @@ async function cmdLive(ctx) {
4218
4337
  if (src) out(src);
4219
4338
  out(disclaimer(t, c));
4220
4339
  }
4221
- async function cmdNext(team, { cfg, t }) {
4340
+ async function cmdNext(team, { cfg, t, now }) {
4222
4341
  precheck(cfg, t);
4223
4342
  const code = resolveTeamArg(team, "Usage: claudinho next <team> (or set CLAUDINHO_TEAM)");
4224
- const fixture = nextFixtureForTeam(code);
4343
+ const fixture = nextFixtureForTeam(code, { from: now ?? /* @__PURE__ */ new Date() });
4225
4344
  if (cfg.json) {
4226
4345
  emitJson({ team: code, fixture: fixture ?? null });
4227
4346
  return;
@@ -4248,32 +4367,30 @@ async function cmdNext(team, { cfg, t }) {
4248
4367
  async function cmdTable(group, ctx) {
4249
4368
  const { cfg, t } = ctx;
4250
4369
  precheck(cfg, t);
4251
- const adapter = adapterFor(ctx);
4252
- const { matches, degraded, source } = await getMatchesForDate(
4253
- adapter,
4254
- localDate((/* @__PURE__ */ new Date()).toISOString(), cfg.tz)
4255
- );
4256
- const wanted = group ? [group.toUpperCase()] : groups(matches);
4370
+ const { tables, degraded, source } = await getStandings(adapterFor(ctx), group);
4257
4371
  if (cfg.json) {
4258
- const tables = wanted.map((g) => ({
4259
- group: g,
4260
- standings: computeStandings(fixturesByGroup(g, matches))
4261
- }));
4372
+ const json = tables.map((tb) => ({ group: tb.group, standings: tb.rows }));
4262
4373
  emitJson({
4263
4374
  degraded,
4264
4375
  source: source ?? null,
4265
- tables: group ? tables[0] ?? null : tables
4376
+ tables: group ? json[0] ?? null : json
4266
4377
  });
4267
4378
  return;
4268
4379
  }
4269
4380
  const c = painterFor(cfg);
4270
- for (const g of wanted) {
4271
- const rows = computeStandings(fixturesByGroup(g, matches));
4272
- if (rows.length === 0) {
4273
- out();
4274
- out(c.dim(" " + t("table.none", { group: g })));
4275
- continue;
4276
- }
4381
+ if (tables.length === 0) {
4382
+ out();
4383
+ out(
4384
+ c.dim(
4385
+ " " + (group ? t("table.none", { group: group.toUpperCase() }) : t("table.empty"))
4386
+ )
4387
+ );
4388
+ out();
4389
+ if (degraded) out(c.dim(" " + t("table.degraded")));
4390
+ out(disclaimer(t, c));
4391
+ return;
4392
+ }
4393
+ for (const { group: g, rows } of tables) {
4277
4394
  out();
4278
4395
  out(header(t("table.title", { group: g }), c));
4279
4396
  const table = new Table({
@@ -4303,6 +4420,7 @@ async function cmdTable(group, ctx) {
4303
4420
  out(table.toString());
4304
4421
  }
4305
4422
  out();
4423
+ if (degraded) out(c.dim(" " + t("table.degraded")));
4306
4424
  const src = dataSource(source, c);
4307
4425
  if (src) out(src);
4308
4426
  out(disclaimer(t, c));
@@ -4398,6 +4516,7 @@ async function cmdMatch(id, ctx) {
4398
4516
  for (const mline of marketBlock(marketSignal, match)) out(" " + c.dim(mline));
4399
4517
  }
4400
4518
  out();
4519
+ if (degraded) out(c.dim(" " + t("feed.degraded")));
4401
4520
  const src = dataSource(liveSource, c);
4402
4521
  if (src) out(src);
4403
4522
  out(disclaimer(t, c));
@@ -4525,6 +4644,7 @@ function emitShare(ctx, e, copy) {
4525
4644
  target: e.target,
4526
4645
  ...e.team ? { team: e.team } : {},
4527
4646
  source: e.input.source ?? null,
4647
+ degraded: e.input.degraded ?? false,
4528
4648
  informationalOnly: true,
4529
4649
  style: e.options.style ?? "social",
4530
4650
  snippet,
@@ -4541,6 +4661,38 @@ function emitShare(ctx, e, copy) {
4541
4661
  );
4542
4662
  }
4543
4663
  }
4664
+ function emitShareTable(ctx, e, copy) {
4665
+ const snippet = formatShareTable(
4666
+ {
4667
+ tables: e.tables,
4668
+ source: e.source,
4669
+ installLine: e.installLine,
4670
+ emptyNote: e.emptyNote,
4671
+ degraded: e.degraded
4672
+ },
4673
+ e.options
4674
+ );
4675
+ if (ctx.cfg.json) {
4676
+ emitJson({
4677
+ kind: "table",
4678
+ target: "table",
4679
+ ...e.group ? { group: e.group } : {},
4680
+ source: e.source ?? null,
4681
+ degraded: e.degraded,
4682
+ informationalOnly: true,
4683
+ snippet,
4684
+ tables: e.tables.map((tb) => ({ group: tb.group, standings: tb.rows }))
4685
+ });
4686
+ } else {
4687
+ out(snippet);
4688
+ }
4689
+ if (copy) {
4690
+ const ok = (ctx.copy ?? copyToClipboard)(snippet);
4691
+ process.stderr.write(
4692
+ (ok ? "Copied share snippet to clipboard." : "Clipboard unavailable; printed snippet instead.") + "\n"
4693
+ );
4694
+ }
4695
+ }
4544
4696
  async function cmdShare(target, team, opts, ctx) {
4545
4697
  const { cfg, t } = ctx;
4546
4698
  const baseOptions = {
@@ -4552,7 +4704,7 @@ async function cmdShare(target, team, opts, ctx) {
4552
4704
  const copy = opts.copy === true;
4553
4705
  if (target === "live") {
4554
4706
  precheck(cfg, t);
4555
- const { matches, source: source2 } = await getLiveMatches(adapterFor(ctx));
4707
+ const { matches, degraded: degraded2, source: source2 } = await getLiveMatches(adapterFor(ctx));
4556
4708
  emitShare(
4557
4709
  ctx,
4558
4710
  {
@@ -4562,7 +4714,9 @@ async function cmdShare(target, team, opts, ctx) {
4562
4714
  title: "Live match pulse",
4563
4715
  matches,
4564
4716
  source: source2,
4565
- emptyNote: "No matches in play right now.",
4717
+ degraded: degraded2,
4718
+ // Degraded ⇒ feed down, not "nothing's on" — say so on the public card.
4719
+ emptyNote: degraded2 ? "Live scores unavailable right now \u2014 couldn't reach the data provider." : "No matches in play right now.",
4566
4720
  installLine: "npx @claudinho/cli live",
4567
4721
  tz: cfg.tz,
4568
4722
  locale: cfg.lang
@@ -4574,10 +4728,30 @@ async function cmdShare(target, team, opts, ctx) {
4574
4728
  );
4575
4729
  return;
4576
4730
  }
4731
+ if (target === "table") {
4732
+ precheck(cfg, t);
4733
+ const group = team?.toUpperCase();
4734
+ const { tables, degraded: degraded2, source: source2 } = await getStandings(adapterFor(ctx), group);
4735
+ emitShareTable(
4736
+ ctx,
4737
+ {
4738
+ group,
4739
+ tables,
4740
+ // Degraded ⇒ a static roster, served by no live provider: no attribution.
4741
+ source: degraded2 ? void 0 : source2,
4742
+ degraded: degraded2,
4743
+ installLine: group ? `npx @claudinho/cli table ${group}` : "npx @claudinho/cli table",
4744
+ emptyNote: group ? `No group ${group}.` : "No standings available.",
4745
+ options: baseOptions
4746
+ },
4747
+ copy
4748
+ );
4749
+ return;
4750
+ }
4577
4751
  if (target === "next") {
4578
4752
  precheck(cfg, t);
4579
4753
  const code = resolveTeamArg(team, "Usage: claudinho share next <team> (or set CLAUDINHO_TEAM)");
4580
- const fixture = nextFixtureForTeam(code);
4754
+ const fixture = nextFixtureForTeam(code, { from: ctx.now ?? /* @__PURE__ */ new Date() });
4581
4755
  const matches = fixture ? [fixture] : [];
4582
4756
  const signals2 = await reliableShareSignals(ctx, matches);
4583
4757
  const teamName = fixture ? fixture.home.code === code ? fixture.home.name : fixture.away.name : code;
@@ -4604,7 +4778,7 @@ async function cmdShare(target, team, opts, ctx) {
4604
4778
  }
4605
4779
  if (target && target !== "today" && !isValidDate(target)) {
4606
4780
  precheck(cfg, t);
4607
- const { match, source: source2 } = await getMatchById(adapterFor(ctx), target);
4781
+ const { match, degraded: degraded2, source: source2 } = await getMatchById(adapterFor(ctx), target);
4608
4782
  const matches = match ? [match] : [];
4609
4783
  const signals2 = await reliableShareSignals(ctx, matches);
4610
4784
  emitShare(
@@ -4617,6 +4791,7 @@ async function cmdShare(target, team, opts, ctx) {
4617
4791
  matches,
4618
4792
  marketSignals: signals2,
4619
4793
  source: source2,
4794
+ degraded: degraded2,
4620
4795
  emptyNote: `No match found with id ${target}.`,
4621
4796
  installLine: `npx @claudinho/cli match ${target}`,
4622
4797
  tz: cfg.tz,
@@ -4631,7 +4806,7 @@ async function cmdShare(target, team, opts, ctx) {
4631
4806
  const explicitDate = target && target !== "today" ? target : void 0;
4632
4807
  precheck(cfg, t, explicitDate);
4633
4808
  const date = explicitDate ?? localDate((/* @__PURE__ */ new Date()).toISOString(), cfg.tz);
4634
- const { matches: all, source } = await getMatchesForDate(adapterFor(ctx), date);
4809
+ const { matches: all, degraded, source } = await getMatchesForDate(adapterFor(ctx), date);
4635
4810
  const todays = fixturesByDate(date, all, cfg.tz);
4636
4811
  const signals = await reliableShareSignals(ctx, todays);
4637
4812
  const human = formatDate(`${date}T12:00:00.000Z`, { tz: cfg.tz, locale: cfg.lang });
@@ -4646,6 +4821,7 @@ async function cmdShare(target, team, opts, ctx) {
4646
4821
  matches: todays,
4647
4822
  marketSignals: signals,
4648
4823
  source,
4824
+ degraded,
4649
4825
  emptyNote: `No matches scheduled for ${human}.`,
4650
4826
  installLine: "npx @claudinho/cli today",
4651
4827
  tz: cfg.tz,
@@ -4726,7 +4902,7 @@ function handlePipeError(stream) {
4726
4902
  }
4727
4903
  handlePipeError(process.stdout);
4728
4904
  handlePipeError(process.stderr);
4729
- var VERSION = "0.4.3";
4905
+ var VERSION = "0.5.1";
4730
4906
  var DISCLAIMER = "Claudinho is an independent fan project. Not affiliated with or endorsed by FIFA or Anthropic.";
4731
4907
  function ctxFrom(cmd) {
4732
4908
  const root = cmd.parent ?? cmd;
@@ -4787,7 +4963,10 @@ program.command("markets").description("show prediction-market signals (read-onl
4787
4963
  fail(e);
4788
4964
  }
4789
4965
  });
4790
- program.command("share").description("print a shareable, copy-pasteable match snippet (#VibingLaVidaLoca)").argument("[target]", '"today" (default), "live", a date, a match id, or "next"').argument("[team]", 'team code when target is "next" (default: $CLAUDINHO_TEAM)').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) => {
4966
+ program.command("share").description("print a shareable, copy-pasteable match snippet (#VibingLaVidaLoca)").argument("[target]", '"today" (default), "live", a date, a match id, "next", or "table"').argument(
4967
+ "[team]",
4968
+ 'team code for "next" (default: $CLAUDINHO_TEAM), or group letter for "table" (omit for all groups)'
4969
+ ).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) => {
4791
4970
  try {
4792
4971
  await cmdShare(target, team, opts, ctxFrom(cmd));
4793
4972
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudinho/cli",
3
- "version": "0.4.3",
3
+ "version": "0.5.1",
4
4
  "description": "Live scores, fixtures, group tables, and read-only prediction-market signals for the 2026 men's football tournament — in your terminal and Claude Code statusline. No API keys. Not affiliated with FIFA or Anthropic.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -56,7 +56,7 @@
56
56
  "tsup": "^8.0.0",
57
57
  "typescript": "^5.7.0",
58
58
  "vitest": "^4.1.0",
59
- "@claudinho/core": "0.4.3"
59
+ "@claudinho/core": "0.5.1"
60
60
  },
61
61
  "scripts": {
62
62
  "build": "tsup",