@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.
- package/README.md +3 -2
- package/dist/index.js +223 -44
- 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
|
|
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
|
|
2795
|
-
for (const
|
|
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 (
|
|
3407
|
-
footer.push(
|
|
3408
|
-
|
|
3409
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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 ?
|
|
4376
|
+
tables: group ? json[0] ?? null : json
|
|
4266
4377
|
});
|
|
4267
4378
|
return;
|
|
4268
4379
|
}
|
|
4269
4380
|
const c = painterFor(cfg);
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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.
|
|
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.
|
|
59
|
+
"@claudinho/core": "0.5.1"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"build": "tsup",
|