@claudinho/cli 0.4.2 → 0.5.0
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 +17 -18
- package/dist/index.js +345 -95
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -20,11 +20,12 @@ npx @claudinho/cli today
|
|
|
20
20
|
```bash
|
|
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
|
-
claudinho next
|
|
24
|
-
claudinho table [GROUP] # group standings (default: all groups)
|
|
23
|
+
claudinho next [TEAM] # a team's next fixture + countdown (default: $CLAUDINHO_TEAM)
|
|
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 snippet: today | live | <date> | <id> | next <TEAM> | table <GROUP>
|
|
28
29
|
claudinho prompt # one compact status line (for statusline/tmux/Starship)
|
|
29
30
|
claudinho init-statusline # wire it into the Claude Code statusline
|
|
30
31
|
claudinho hook # live-score context for a Claude Code hook (silent off-match)
|
|
@@ -77,7 +78,7 @@ market-implied percentages — for a date, a match, or a team's next fixture:
|
|
|
77
78
|
claudinho markets # today's signals
|
|
78
79
|
claudinho markets 2026-06-11 # a specific date
|
|
79
80
|
claudinho markets 760415 # one match by id
|
|
80
|
-
claudinho markets next MEX # a team's next fixture
|
|
81
|
+
claudinho markets next MEX # a team's current-or-next fixture (in-play preferred)
|
|
81
82
|
claudinho markets today --json # structured sidecar output
|
|
82
83
|
```
|
|
83
84
|
|
|
@@ -91,11 +92,12 @@ Opt out with `--no-markets` (per command) or `CLAUDINHO_MARKETS=off` (global). T
|
|
|
91
92
|
statusline and hook **never** show market data — it stays off the hot path.
|
|
92
93
|
|
|
93
94
|
> **How matches are matched:** event slugs are derived automatically from each
|
|
94
|
-
> fixture (`fifwc-{home}-{away}-{date}`), so real
|
|
95
|
+
> fixture (`fifwc-{home}-{away}-{date}`), so real signals appear for any match with a
|
|
95
96
|
> live Polymarket market — no mapping needed (`mapping.2026.json` is for slug
|
|
96
97
|
> *overrides* only). Matching fails closed, so an unmatched fixture simply shows
|
|
97
|
-
> nothing
|
|
98
|
-
>
|
|
98
|
+
> nothing — and finished matches never show one (market signals are pre-match
|
|
99
|
+
> and in-play reads). For an offline preview, set `CLAUDINHO_MARKETS_SOURCE=fake`
|
|
100
|
+
> to render clearly-labeled synthetic **"demo data"** signals.
|
|
99
101
|
|
|
100
102
|
## Shareable snippets
|
|
101
103
|
|
|
@@ -106,26 +108,23 @@ social posts, READMEs, and issue comments — your terminal football, ready to p
|
|
|
106
108
|
claudinho share # today's matches
|
|
107
109
|
claudinho share live # matches in play
|
|
108
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)
|
|
109
112
|
claudinho share 760415 # one match by id
|
|
110
113
|
claudinho share next MEX --copy # …and copy it straight to the clipboard
|
|
111
114
|
```
|
|
112
115
|
|
|
113
|
-
<!-- DEMO CARD: verbatim output of `claudinho share next
|
|
116
|
+
<!-- DEMO CARD: verbatim output of `claudinho share next USA --tz America/Los_Angeles`.
|
|
114
117
|
REGENERATE immediately before merging — the market block is gate-conditional
|
|
115
118
|
and the numbers drift. Never hand-edit. -->
|
|
116
119
|
```text
|
|
117
|
-
Next up for
|
|
120
|
+
Next up for United States
|
|
118
121
|
|
|
119
|
-
|
|
120
|
-
Jun
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
Prediction markets favor Mexico.
|
|
124
|
-
Mexico 69% · Draw 20% · South Africa 10%
|
|
125
|
-
Source: Polymarket · updated 08:15 UTC · informational only
|
|
122
|
+
🇺🇸 United States vs Paraguay 🇵🇾
|
|
123
|
+
Jun 12 · 18:00 America/Los_Angeles
|
|
124
|
+
SoFi Stadium, Inglewood, California, USA
|
|
126
125
|
|
|
127
126
|
#VibingLaVidaLoca · Independent fan project · not affiliated with FIFA or Anthropic.
|
|
128
|
-
Try it: npx @claudinho/cli next
|
|
127
|
+
Try it: npx @claudinho/cli next USA
|
|
129
128
|
```
|
|
130
129
|
|
|
131
130
|
Snippets are **plain text** (no color codes — they paste cleanly everywhere) and
|
|
@@ -161,7 +160,7 @@ The statusline reads from a local micro-cache and **never blocks on the
|
|
|
161
160
|
network** (<150ms). When several matches are live it shows them all inline:
|
|
162
161
|
`⚽ 🇳🇴 1–1 🇫🇷 87' · 🇸🇳 1–2 🇮🇶 86'`. Customize via env:
|
|
163
162
|
|
|
164
|
-
- `CLAUDINHO_TEAM=MEX` — show only your team's match
|
|
163
|
+
- `CLAUDINHO_TEAM=MEX` — show only your team's match; also the default team for `next`, `markets next`, and `share next` when the argument is omitted
|
|
165
164
|
- `CLAUDINHO_MAX=2` — cap how many live matches show inline (rest collapse to `+N`; default: all)
|
|
166
165
|
- `CLAUDINHO_COMPACT=0` — show 3-letter codes alongside flags
|
|
167
166
|
|
package/dist/index.js
CHANGED
|
@@ -2610,6 +2610,13 @@ function nextFixtureForTeam(code, opts = {}) {
|
|
|
2610
2610
|
(m) => new Date(m.kickoff).getTime() >= from.getTime()
|
|
2611
2611
|
);
|
|
2612
2612
|
}
|
|
2613
|
+
var LIVE_WINDOW_MS = 140 * 6e4;
|
|
2614
|
+
function fixturesInLiveWindow(now = Date.now(), fixtures = SCHEDULE) {
|
|
2615
|
+
return fixtures.filter((m) => {
|
|
2616
|
+
const k = Date.parse(m.kickoff);
|
|
2617
|
+
return now >= k && now <= k + LIVE_WINDOW_MS;
|
|
2618
|
+
}).sort(byKickoff);
|
|
2619
|
+
}
|
|
2613
2620
|
function groups(fixtures = SCHEDULE) {
|
|
2614
2621
|
const set = /* @__PURE__ */ new Set();
|
|
2615
2622
|
for (const m of fixtures) if (m.group) set.add(m.group);
|
|
@@ -2755,6 +2762,43 @@ function mapEspnEvent(ev, ctx = {}) {
|
|
|
2755
2762
|
function toEspnDate(d) {
|
|
2756
2763
|
return d.replace(/\D/g, "").slice(0, 8);
|
|
2757
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
|
+
}
|
|
2758
2802
|
var EspnAdapter = class {
|
|
2759
2803
|
constructor(opts = {}) {
|
|
2760
2804
|
this.opts = opts;
|
|
@@ -2774,25 +2818,30 @@ var EspnAdapter = class {
|
|
|
2774
2818
|
const today = await this.fetchScoreboard();
|
|
2775
2819
|
return today.filter((m) => m.status === "LIVE" || m.status === "HT");
|
|
2776
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
|
+
}
|
|
2777
2834
|
/**
|
|
2778
2835
|
* Build (and cache) a team-code -> group-letter map from the standings
|
|
2779
|
-
* 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.
|
|
2780
2838
|
*/
|
|
2781
2839
|
async fetchGroupMap(force = false) {
|
|
2782
2840
|
if (this.groupMap && !force) return this.groupMap;
|
|
2783
|
-
const base = this.opts.baseUrl ?? DEFAULT_BASE;
|
|
2784
|
-
const standingsUrl = `${base.replace("/apis/site/v2/", "/apis/v2/")}/standings`;
|
|
2785
2841
|
const map = {};
|
|
2786
2842
|
try {
|
|
2787
|
-
const
|
|
2788
|
-
for (const
|
|
2789
|
-
const letter = (child.name ?? child.abbreviation ?? "").match(/Group\s+([A-L])/i)?.[1]?.toUpperCase();
|
|
2790
|
-
if (!letter) continue;
|
|
2791
|
-
for (const e of child.standings?.entries ?? []) {
|
|
2792
|
-
const code = e.team?.abbreviation?.toUpperCase();
|
|
2793
|
-
if (code) map[code] = letter;
|
|
2794
|
-
}
|
|
2795
|
-
}
|
|
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;
|
|
2796
2845
|
} catch {
|
|
2797
2846
|
}
|
|
2798
2847
|
this.groupMap = map;
|
|
@@ -2863,10 +2912,53 @@ async function getMatchesForDate(adapter, dateISO) {
|
|
|
2863
2912
|
return { matches: base, degraded: true };
|
|
2864
2913
|
}
|
|
2865
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
|
+
}
|
|
2866
2931
|
function shiftUtcDate(dateISO, days) {
|
|
2867
2932
|
const [y, m, d] = dateISO.slice(0, 10).split("-").map(Number);
|
|
2868
2933
|
return new Date(Date.UTC(y ?? 1970, (m ?? 1) - 1, (d ?? 1) + days)).toISOString().slice(0, 10);
|
|
2869
2934
|
}
|
|
2935
|
+
var EXTRA_TIME_SLACK_MS = 60 * 6e4;
|
|
2936
|
+
async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Date()) {
|
|
2937
|
+
const nowMs = now.getTime();
|
|
2938
|
+
const candidate = fixturesByTeam(code).find((m) => {
|
|
2939
|
+
const k = Date.parse(m.kickoff);
|
|
2940
|
+
return nowMs >= k && nowMs <= k + LIVE_WINDOW_MS + EXTRA_TIME_SLACK_MS;
|
|
2941
|
+
});
|
|
2942
|
+
if (candidate) {
|
|
2943
|
+
const r = await getMatchById(adapter, candidate.id);
|
|
2944
|
+
const m = r.match ?? candidate;
|
|
2945
|
+
if (!isFinished(m.status)) return { ...r, match: m };
|
|
2946
|
+
}
|
|
2947
|
+
const next = nextFixtureForTeam(code, { from: now });
|
|
2948
|
+
return { match: next, degraded: false };
|
|
2949
|
+
}
|
|
2950
|
+
async function getMatchById(adapter, id) {
|
|
2951
|
+
const base = allFixtures().find((m) => m.id === id);
|
|
2952
|
+
if (!base) return { match: void 0, degraded: false };
|
|
2953
|
+
const day = base.kickoff.slice(0, 10);
|
|
2954
|
+
try {
|
|
2955
|
+
const live = adapter.fetchWindow ? await adapter.fetchWindow(shiftUtcDate(day, -1), shiftUtcDate(day, 1)) : await adapter.fetchByDate(day);
|
|
2956
|
+
const hit = live.find((m) => m.id === id);
|
|
2957
|
+
return { match: hit ?? base, degraded: false, source: hit ? adapter.name : void 0 };
|
|
2958
|
+
} catch {
|
|
2959
|
+
return { match: base, degraded: true };
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2870
2962
|
async function getLiveMatches(adapter) {
|
|
2871
2963
|
try {
|
|
2872
2964
|
return { matches: await adapter.fetchLive(), degraded: false, source: adapter.name };
|
|
@@ -2875,6 +2967,12 @@ async function getLiveMatches(adapter) {
|
|
|
2875
2967
|
}
|
|
2876
2968
|
}
|
|
2877
2969
|
var DEFAULT_MAX_AGE_MS = 15 * 6e4;
|
|
2970
|
+
function marketRelevant(match, now = /* @__PURE__ */ new Date()) {
|
|
2971
|
+
if (isLive(match.status)) return true;
|
|
2972
|
+
if (isFinished(match.status)) return false;
|
|
2973
|
+
const k = Date.parse(match.kickoff);
|
|
2974
|
+
return !Number.isFinite(k) || now.getTime() <= k + LIVE_WINDOW_MS;
|
|
2975
|
+
}
|
|
2878
2976
|
function normalizeOutcomes(outcomes) {
|
|
2879
2977
|
const sum = outcomes.reduce(
|
|
2880
2978
|
(s, o) => s + (Number.isFinite(o.probability) && o.probability > 0 ? o.probability : 0),
|
|
@@ -3362,11 +3460,50 @@ function formatShareSnippet(input, options = {}) {
|
|
|
3362
3460
|
blocks.push(card.join("\n"));
|
|
3363
3461
|
}
|
|
3364
3462
|
}
|
|
3463
|
+
blocks.push(
|
|
3464
|
+
shareFooter({
|
|
3465
|
+
source: input.source,
|
|
3466
|
+
installLine: input.installLine,
|
|
3467
|
+
includeHashtag,
|
|
3468
|
+
includeInstall
|
|
3469
|
+
})
|
|
3470
|
+
);
|
|
3471
|
+
return blocks.join("\n\n");
|
|
3472
|
+
}
|
|
3473
|
+
function shareFooter(opts) {
|
|
3365
3474
|
const footer = [];
|
|
3366
|
-
if (
|
|
3367
|
-
footer.push(
|
|
3368
|
-
|
|
3369
|
-
|
|
3475
|
+
if (opts.source) footer.push(`Live data: ${liveSourceLabel(opts.source)}`);
|
|
3476
|
+
footer.push(
|
|
3477
|
+
[opts.includeHashtag ? SHARE_HASHTAG : "", SHARE_DISCLAIMER].filter(Boolean).join(" \xB7 ")
|
|
3478
|
+
);
|
|
3479
|
+
if (opts.includeInstall && opts.installLine) footer.push(`Try it: ${opts.installLine}`);
|
|
3480
|
+
return footer.join("\n");
|
|
3481
|
+
}
|
|
3482
|
+
function gd(n) {
|
|
3483
|
+
return n > 0 ? `+${n}` : `${n}`;
|
|
3484
|
+
}
|
|
3485
|
+
function tableRow(r, rank) {
|
|
3486
|
+
return `${rank}. ${r.team.flag} ${r.team.code} ${r.points} pts \xB7 ${r.won}-${r.drawn}-${r.lost} \xB7 ${gd(r.goalDiff)}`;
|
|
3487
|
+
}
|
|
3488
|
+
function formatShareTable(input, options = {}) {
|
|
3489
|
+
const includeHashtag = options.includeHashtag !== false;
|
|
3490
|
+
const includeInstall = options.includeInstallLine !== false;
|
|
3491
|
+
const blocks = [];
|
|
3492
|
+
if (input.tables.length === 0) {
|
|
3493
|
+
blocks.push(input.emptyNote ?? "No standings available.");
|
|
3494
|
+
} else {
|
|
3495
|
+
for (const { group, rows } of input.tables) {
|
|
3496
|
+
blocks.push(
|
|
3497
|
+
[`Group ${group} \xB7 standings`, "", ...rows.map((r, i) => tableRow(r, i + 1))].join("\n")
|
|
3498
|
+
);
|
|
3499
|
+
}
|
|
3500
|
+
if (input.degraded) {
|
|
3501
|
+
blocks.push("(Live standings unavailable \u2014 group roster, not live results.)");
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
blocks.push(
|
|
3505
|
+
shareFooter({ source: input.source, installLine: input.installLine, includeHashtag, includeInstall })
|
|
3506
|
+
);
|
|
3370
3507
|
return blocks.join("\n\n");
|
|
3371
3508
|
}
|
|
3372
3509
|
|
|
@@ -3425,6 +3562,8 @@ var EN = {
|
|
|
3425
3562
|
"next.in": "in {countdown}",
|
|
3426
3563
|
"table.title": "Group {group}",
|
|
3427
3564
|
"table.none": "No group found for {group}.",
|
|
3565
|
+
"table.degraded": "Live standings unavailable \u2014 showing the group roster.",
|
|
3566
|
+
"table.empty": "No standings available.",
|
|
3428
3567
|
"match.none": "No match found with id {id}.",
|
|
3429
3568
|
"status.scheduled": "scheduled",
|
|
3430
3569
|
"status.live": "LIVE",
|
|
@@ -3455,6 +3594,8 @@ var ES = {
|
|
|
3455
3594
|
"next.in": "en {countdown}",
|
|
3456
3595
|
"table.title": "Grupo {group}",
|
|
3457
3596
|
"table.none": "No se encontr\xF3 el grupo {group}.",
|
|
3597
|
+
"table.degraded": "Tabla en vivo no disponible \u2014 mostrando la lista del grupo.",
|
|
3598
|
+
"table.empty": "No hay clasificaci\xF3n disponible.",
|
|
3458
3599
|
"match.none": "No se encontr\xF3 partido con id {id}.",
|
|
3459
3600
|
"status.scheduled": "programado",
|
|
3460
3601
|
"status.live": "EN VIVO",
|
|
@@ -3485,6 +3626,8 @@ var PT = {
|
|
|
3485
3626
|
"next.in": "em {countdown}",
|
|
3486
3627
|
"table.title": "Grupo {group}",
|
|
3487
3628
|
"table.none": "Grupo {group} n\xE3o encontrado.",
|
|
3629
|
+
"table.degraded": "Classifica\xE7\xE3o ao vivo indispon\xEDvel \u2014 mostrando os times do grupo.",
|
|
3630
|
+
"table.empty": "Classifica\xE7\xE3o indispon\xEDvel.",
|
|
3488
3631
|
"match.none": "Nenhum jogo encontrado com id {id}.",
|
|
3489
3632
|
"status.scheduled": "agendado",
|
|
3490
3633
|
"status.live": "AO VIVO",
|
|
@@ -3515,6 +3658,8 @@ var FR = {
|
|
|
3515
3658
|
"next.in": "dans {countdown}",
|
|
3516
3659
|
"table.title": "Groupe {group}",
|
|
3517
3660
|
"table.none": "Groupe {group} introuvable.",
|
|
3661
|
+
"table.degraded": "Classement en direct indisponible \u2014 affichage de la composition du groupe.",
|
|
3662
|
+
"table.empty": "Aucun classement disponible.",
|
|
3518
3663
|
"match.none": "Aucun match trouv\xE9 avec id {id}.",
|
|
3519
3664
|
"status.scheduled": "pr\xE9vu",
|
|
3520
3665
|
"status.live": "DIRECT",
|
|
@@ -3812,15 +3957,10 @@ function releaseLock() {
|
|
|
3812
3957
|
}
|
|
3813
3958
|
|
|
3814
3959
|
// src/statusline.ts
|
|
3815
|
-
var LIVE_WINDOW_MS = 140 * 6e4;
|
|
3816
3960
|
var DISPLAY_STALE_MS = 5 * 6e4;
|
|
3817
3961
|
var LIVE_TTL_MS = 15e3;
|
|
3818
3962
|
function inLiveWindow(now = Date.now(), fixtures = allFixtures()) {
|
|
3819
|
-
|
|
3820
|
-
const k = Date.parse(m.kickoff);
|
|
3821
|
-
if (now >= k && now <= k + LIVE_WINDOW_MS) return true;
|
|
3822
|
-
}
|
|
3823
|
-
return false;
|
|
3963
|
+
return fixturesInLiveWindow(now, fixtures).length > 0;
|
|
3824
3964
|
}
|
|
3825
3965
|
function nextOverall(now, fixtures = allFixtures()) {
|
|
3826
3966
|
return [...fixtures].sort(byKickoff).find((m) => Date.parse(m.kickoff) >= now);
|
|
@@ -3855,6 +3995,17 @@ function renderPrompt(state, opts = {}) {
|
|
|
3855
3995
|
if (overflow > 0) line2 += ` +${overflow}`;
|
|
3856
3996
|
return line2;
|
|
3857
3997
|
}
|
|
3998
|
+
const cacheFresh = !!state && state.degraded !== true && ageMs(state, nowMs) < DISPLAY_STALE_MS;
|
|
3999
|
+
if (!cacheFresh) {
|
|
4000
|
+
const win = fixturesInLiveWindow(nowMs).filter(
|
|
4001
|
+
(m) => !team || m.home.code === team || m.away.code === team
|
|
4002
|
+
);
|
|
4003
|
+
const first = win[0];
|
|
4004
|
+
if (first) {
|
|
4005
|
+
const more = win.length - 1;
|
|
4006
|
+
return `\u26BD ${first.home.flag} vs ${first.away.flag} live \xB7 syncing\u2026` + (more > 0 ? ` +${more}` : "");
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
3858
4009
|
const next = team ? nextFixtureForTeam(team, { from: now }) : nextOverall(nowMs);
|
|
3859
4010
|
if (next) {
|
|
3860
4011
|
return `${next.home.flag} vs ${next.away.flag} in ${countdown(next.kickoff, now)}`;
|
|
@@ -4070,8 +4221,10 @@ async function marketSignalsFor(ctx, matches, opts = {}) {
|
|
|
4070
4221
|
}
|
|
4071
4222
|
async function reliableMarketSignals(ctx, matches) {
|
|
4072
4223
|
if (ctx.cfg.markets === false) return /* @__PURE__ */ new Map();
|
|
4073
|
-
const
|
|
4074
|
-
const
|
|
4224
|
+
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
4225
|
+
const relevant = matches.filter((m) => marketRelevant(m, now));
|
|
4226
|
+
if (relevant.length === 0) return /* @__PURE__ */ new Map();
|
|
4227
|
+
const raw = await marketSignalsFor(ctx, relevant, DEFAULT_ON_MARKET_OPTS);
|
|
4075
4228
|
const out2 = /* @__PURE__ */ new Map();
|
|
4076
4229
|
for (const [id, s] of raw) if (isReliableMarketSignal(s, { now })) out2.set(id, s);
|
|
4077
4230
|
return out2;
|
|
@@ -4094,10 +4247,22 @@ function precheck(cfg, t, date) {
|
|
|
4094
4247
|
if (cfg.tz && !isValidTimeZone(cfg.tz)) {
|
|
4095
4248
|
process.stderr.write(t("warn.tz", { tz: cfg.tz }) + "\n");
|
|
4096
4249
|
}
|
|
4250
|
+
const competition = resolveCompetition();
|
|
4251
|
+
if (competition !== DEFAULT_COMPETITION) {
|
|
4252
|
+
process.stderr.write(
|
|
4253
|
+
`claudinho: CLAUDINHO_COMPETITION=${competition} \u2014 live data follows a different competition than the bundled 2026 schedule.
|
|
4254
|
+
`
|
|
4255
|
+
);
|
|
4256
|
+
}
|
|
4097
4257
|
if (date !== void 0 && !isValidDate(date)) {
|
|
4098
4258
|
throw new InputError(t("err.date", { date }));
|
|
4099
4259
|
}
|
|
4100
4260
|
}
|
|
4261
|
+
function resolveTeamArg(team, usage) {
|
|
4262
|
+
const code = team ?? process.env.CLAUDINHO_TEAM;
|
|
4263
|
+
if (!code) throw new InputError(usage);
|
|
4264
|
+
return code.toUpperCase();
|
|
4265
|
+
}
|
|
4101
4266
|
async function cmdToday(date, ctx) {
|
|
4102
4267
|
const { cfg, t } = ctx;
|
|
4103
4268
|
precheck(cfg, t, date);
|
|
@@ -4158,10 +4323,10 @@ async function cmdLive(ctx) {
|
|
|
4158
4323
|
if (src) out(src);
|
|
4159
4324
|
out(disclaimer(t, c));
|
|
4160
4325
|
}
|
|
4161
|
-
async function cmdNext(team, { cfg, t }) {
|
|
4326
|
+
async function cmdNext(team, { cfg, t, now }) {
|
|
4162
4327
|
precheck(cfg, t);
|
|
4163
|
-
const code = team
|
|
4164
|
-
const fixture = nextFixtureForTeam(code);
|
|
4328
|
+
const code = resolveTeamArg(team, "Usage: claudinho next <team> (or set CLAUDINHO_TEAM)");
|
|
4329
|
+
const fixture = nextFixtureForTeam(code, { from: now ?? /* @__PURE__ */ new Date() });
|
|
4165
4330
|
if (cfg.json) {
|
|
4166
4331
|
emitJson({ team: code, fixture: fixture ?? null });
|
|
4167
4332
|
return;
|
|
@@ -4188,32 +4353,30 @@ async function cmdNext(team, { cfg, t }) {
|
|
|
4188
4353
|
async function cmdTable(group, ctx) {
|
|
4189
4354
|
const { cfg, t } = ctx;
|
|
4190
4355
|
precheck(cfg, t);
|
|
4191
|
-
const
|
|
4192
|
-
const { matches, degraded, source } = await getMatchesForDate(
|
|
4193
|
-
adapter,
|
|
4194
|
-
localDate((/* @__PURE__ */ new Date()).toISOString(), cfg.tz)
|
|
4195
|
-
);
|
|
4196
|
-
const wanted = group ? [group.toUpperCase()] : groups(matches);
|
|
4356
|
+
const { tables, degraded, source } = await getStandings(adapterFor(ctx), group);
|
|
4197
4357
|
if (cfg.json) {
|
|
4198
|
-
const
|
|
4199
|
-
group: g,
|
|
4200
|
-
standings: computeStandings(fixturesByGroup(g, matches))
|
|
4201
|
-
}));
|
|
4358
|
+
const json = tables.map((tb) => ({ group: tb.group, standings: tb.rows }));
|
|
4202
4359
|
emitJson({
|
|
4203
4360
|
degraded,
|
|
4204
4361
|
source: source ?? null,
|
|
4205
|
-
tables: group ?
|
|
4362
|
+
tables: group ? json[0] ?? null : json
|
|
4206
4363
|
});
|
|
4207
4364
|
return;
|
|
4208
4365
|
}
|
|
4209
4366
|
const c = painterFor(cfg);
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4367
|
+
if (tables.length === 0) {
|
|
4368
|
+
out();
|
|
4369
|
+
out(
|
|
4370
|
+
c.dim(
|
|
4371
|
+
" " + (group ? t("table.none", { group: group.toUpperCase() }) : t("table.empty"))
|
|
4372
|
+
)
|
|
4373
|
+
);
|
|
4374
|
+
out();
|
|
4375
|
+
if (degraded) out(c.dim(" " + t("table.degraded")));
|
|
4376
|
+
out(disclaimer(t, c));
|
|
4377
|
+
return;
|
|
4378
|
+
}
|
|
4379
|
+
for (const { group: g, rows } of tables) {
|
|
4217
4380
|
out();
|
|
4218
4381
|
out(header(t("table.title", { group: g }), c));
|
|
4219
4382
|
const table = new Table({
|
|
@@ -4243,6 +4406,7 @@ async function cmdTable(group, ctx) {
|
|
|
4243
4406
|
out(table.toString());
|
|
4244
4407
|
}
|
|
4245
4408
|
out();
|
|
4409
|
+
if (degraded) out(c.dim(" " + t("table.degraded")));
|
|
4246
4410
|
const src = dataSource(source, c);
|
|
4247
4411
|
if (src) out(src);
|
|
4248
4412
|
out(disclaimer(t, c));
|
|
@@ -4298,20 +4462,15 @@ function cmdInitHook(opts, { cfg }) {
|
|
|
4298
4462
|
async function cmdMatch(id, ctx) {
|
|
4299
4463
|
const { cfg, t } = ctx;
|
|
4300
4464
|
precheck(cfg, t);
|
|
4301
|
-
const
|
|
4302
|
-
let match = allFixtures().find((m) => m.id === id);
|
|
4303
|
-
let liveSource;
|
|
4304
|
-
try {
|
|
4305
|
-
if (match) {
|
|
4306
|
-
const live = await adapter.fetchByDate(match.kickoff.slice(0, 10));
|
|
4307
|
-
match = live.find((m) => m.id === id) ?? match;
|
|
4308
|
-
liveSource = adapter.name;
|
|
4309
|
-
}
|
|
4310
|
-
} catch {
|
|
4311
|
-
}
|
|
4465
|
+
const { match, degraded, source: liveSource } = await getMatchById(adapterFor(ctx), id);
|
|
4312
4466
|
const marketSignal = match ? await reliableMarketSignalFor(ctx, match) : void 0;
|
|
4313
4467
|
if (cfg.json) {
|
|
4314
|
-
emitJson({
|
|
4468
|
+
emitJson({
|
|
4469
|
+
degraded,
|
|
4470
|
+
match: match ?? null,
|
|
4471
|
+
source: liveSource ?? null,
|
|
4472
|
+
marketSignal: marketSignal ?? null
|
|
4473
|
+
});
|
|
4315
4474
|
return;
|
|
4316
4475
|
}
|
|
4317
4476
|
const c = painterFor(cfg);
|
|
@@ -4351,8 +4510,13 @@ var MARKET_INFO = "Prediction-market data is informational only.";
|
|
|
4351
4510
|
function marketDisplayable(sig) {
|
|
4352
4511
|
return !sig.ambiguous && sig.favorite != null && hasSaneDistribution(sig.outcomes);
|
|
4353
4512
|
}
|
|
4354
|
-
function marketHeaderLine(m) {
|
|
4355
|
-
|
|
4513
|
+
function marketHeaderLine(m, cfg) {
|
|
4514
|
+
const when = formatDate(m.kickoff, { tz: cfg.tz, locale: cfg.lang });
|
|
4515
|
+
return `${m.home.flag} ${m.home.name} vs ${m.away.name} ${m.away.flag} \xB7 ${when}`;
|
|
4516
|
+
}
|
|
4517
|
+
function noSignalLine(m, now) {
|
|
4518
|
+
if (marketRelevant(m, now)) return "No market signal for this match.";
|
|
4519
|
+
return isFinished(m.status) ? "Match has finished \u2014 market signals are pre-match and in-play reads." : "Match appears to have finished \u2014 market signals are pre-match and in-play reads.";
|
|
4356
4520
|
}
|
|
4357
4521
|
function printMarketBlock(m, sig, c) {
|
|
4358
4522
|
for (const line2 of marketBlock(sig, m)) out(" " + c.dim(line2));
|
|
@@ -4361,10 +4525,10 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
4361
4525
|
const { cfg, t } = ctx;
|
|
4362
4526
|
if (target === "next") {
|
|
4363
4527
|
precheck(cfg, t);
|
|
4364
|
-
|
|
4365
|
-
const
|
|
4366
|
-
const fixture =
|
|
4367
|
-
const sig = fixture ? (await marketSignalsFor(ctx, [fixture], MARKETS_CMD_OPTS)).get(fixture.id) : void 0;
|
|
4528
|
+
const code = resolveTeamArg(team, "Usage: claudinho markets next <team> (or set CLAUDINHO_TEAM)");
|
|
4529
|
+
const now2 = ctx.now ?? /* @__PURE__ */ new Date();
|
|
4530
|
+
const { match: fixture } = await marketFixtureForTeam(adapterFor(ctx), code, now2);
|
|
4531
|
+
const sig = fixture && marketRelevant(fixture, now2) ? (await marketSignalsFor(ctx, [fixture], MARKETS_CMD_OPTS)).get(fixture.id) : void 0;
|
|
4368
4532
|
const shown = sig && marketDisplayable(sig) ? sig : void 0;
|
|
4369
4533
|
if (cfg.json) {
|
|
4370
4534
|
emitJson({
|
|
@@ -4380,10 +4544,10 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
4380
4544
|
if (!fixture) {
|
|
4381
4545
|
out(c2.dim(" " + t("next.none", { team: code })));
|
|
4382
4546
|
} else {
|
|
4383
|
-
out(header(marketHeaderLine(fixture), c2));
|
|
4547
|
+
out(header(marketHeaderLine(fixture, cfg), c2));
|
|
4384
4548
|
out();
|
|
4385
4549
|
if (shown) printMarketBlock(fixture, shown, c2);
|
|
4386
|
-
else out(c2.dim("
|
|
4550
|
+
else out(c2.dim(" " + noSignalLine(fixture, now2)));
|
|
4387
4551
|
}
|
|
4388
4552
|
out();
|
|
4389
4553
|
out(disclaimer(t, c2));
|
|
@@ -4392,8 +4556,9 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
4392
4556
|
}
|
|
4393
4557
|
if (target && target !== "today" && !isValidDate(target)) {
|
|
4394
4558
|
precheck(cfg, t);
|
|
4395
|
-
const
|
|
4396
|
-
const
|
|
4559
|
+
const now2 = ctx.now ?? /* @__PURE__ */ new Date();
|
|
4560
|
+
const { match } = await getMatchById(adapterFor(ctx), target);
|
|
4561
|
+
const sig = match && marketRelevant(match, now2) ? (await marketSignalsFor(ctx, [match], MARKETS_CMD_OPTS)).get(match.id) : void 0;
|
|
4397
4562
|
const shown = sig && marketDisplayable(sig) ? sig : void 0;
|
|
4398
4563
|
if (cfg.json) {
|
|
4399
4564
|
emitJson({ matchId: target, informationalOnly: true, signal: shown ?? null });
|
|
@@ -4404,10 +4569,10 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
4404
4569
|
if (!match) {
|
|
4405
4570
|
out(c2.dim(" " + t("match.none", { id: target })));
|
|
4406
4571
|
} else {
|
|
4407
|
-
out(header(marketHeaderLine(match), c2));
|
|
4572
|
+
out(header(marketHeaderLine(match, cfg), c2));
|
|
4408
4573
|
out();
|
|
4409
4574
|
if (shown) printMarketBlock(match, shown, c2);
|
|
4410
|
-
else out(c2.dim("
|
|
4575
|
+
else out(c2.dim(" " + noSignalLine(match, now2)));
|
|
4411
4576
|
}
|
|
4412
4577
|
out();
|
|
4413
4578
|
out(disclaimer(t, c2));
|
|
@@ -4416,11 +4581,13 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
4416
4581
|
}
|
|
4417
4582
|
const explicitDate = target && target !== "today" ? target : void 0;
|
|
4418
4583
|
precheck(cfg, t, explicitDate);
|
|
4419
|
-
const
|
|
4584
|
+
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
4585
|
+
const date = explicitDate ?? localDate(now.toISOString(), cfg.tz);
|
|
4420
4586
|
const { matches } = await getMatchesForDate(adapterFor(ctx), date);
|
|
4421
4587
|
const todays = fixturesByDate(date, matches, cfg.tz);
|
|
4422
|
-
const
|
|
4423
|
-
const
|
|
4588
|
+
const relevant = todays.filter((m) => marketRelevant(m, now));
|
|
4589
|
+
const signals = await marketSignalsFor(ctx, relevant, MARKETS_CMD_OPTS);
|
|
4590
|
+
const rows = relevant.map((m) => ({ match: m, signal: signals.get(m.id) })).filter(
|
|
4424
4591
|
(r) => !!r.signal && marketDisplayable(r.signal)
|
|
4425
4592
|
);
|
|
4426
4593
|
if (cfg.json) {
|
|
@@ -4437,7 +4604,7 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
4437
4604
|
out(c.dim(` No market signals available for ${date}.`));
|
|
4438
4605
|
} else {
|
|
4439
4606
|
for (const { match, signal } of rows) {
|
|
4440
|
-
out(" " + c.bold(marketHeaderLine(match)));
|
|
4607
|
+
out(" " + c.bold(marketHeaderLine(match, cfg)));
|
|
4441
4608
|
printMarketBlock(match, signal, c);
|
|
4442
4609
|
out();
|
|
4443
4610
|
}
|
|
@@ -4478,6 +4645,38 @@ function emitShare(ctx, e, copy) {
|
|
|
4478
4645
|
);
|
|
4479
4646
|
}
|
|
4480
4647
|
}
|
|
4648
|
+
function emitShareTable(ctx, e, copy) {
|
|
4649
|
+
const snippet = formatShareTable(
|
|
4650
|
+
{
|
|
4651
|
+
tables: e.tables,
|
|
4652
|
+
source: e.source,
|
|
4653
|
+
installLine: e.installLine,
|
|
4654
|
+
emptyNote: e.emptyNote,
|
|
4655
|
+
degraded: e.degraded
|
|
4656
|
+
},
|
|
4657
|
+
e.options
|
|
4658
|
+
);
|
|
4659
|
+
if (ctx.cfg.json) {
|
|
4660
|
+
emitJson({
|
|
4661
|
+
kind: "table",
|
|
4662
|
+
target: "table",
|
|
4663
|
+
...e.group ? { group: e.group } : {},
|
|
4664
|
+
source: e.source ?? null,
|
|
4665
|
+
degraded: e.degraded,
|
|
4666
|
+
informationalOnly: true,
|
|
4667
|
+
snippet,
|
|
4668
|
+
tables: e.tables.map((tb) => ({ group: tb.group, standings: tb.rows }))
|
|
4669
|
+
});
|
|
4670
|
+
} else {
|
|
4671
|
+
out(snippet);
|
|
4672
|
+
}
|
|
4673
|
+
if (copy) {
|
|
4674
|
+
const ok = (ctx.copy ?? copyToClipboard)(snippet);
|
|
4675
|
+
process.stderr.write(
|
|
4676
|
+
(ok ? "Copied share snippet to clipboard." : "Clipboard unavailable; printed snippet instead.") + "\n"
|
|
4677
|
+
);
|
|
4678
|
+
}
|
|
4679
|
+
}
|
|
4481
4680
|
async function cmdShare(target, team, opts, ctx) {
|
|
4482
4681
|
const { cfg, t } = ctx;
|
|
4483
4682
|
const baseOptions = {
|
|
@@ -4511,11 +4710,30 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
4511
4710
|
);
|
|
4512
4711
|
return;
|
|
4513
4712
|
}
|
|
4713
|
+
if (target === "table") {
|
|
4714
|
+
precheck(cfg, t);
|
|
4715
|
+
const group = team?.toUpperCase();
|
|
4716
|
+
const { tables, degraded, source: source2 } = await getStandings(adapterFor(ctx), group);
|
|
4717
|
+
emitShareTable(
|
|
4718
|
+
ctx,
|
|
4719
|
+
{
|
|
4720
|
+
group,
|
|
4721
|
+
tables,
|
|
4722
|
+
// Degraded ⇒ a static roster, served by no live provider: no attribution.
|
|
4723
|
+
source: degraded ? void 0 : source2,
|
|
4724
|
+
degraded,
|
|
4725
|
+
installLine: group ? `npx @claudinho/cli table ${group}` : "npx @claudinho/cli table",
|
|
4726
|
+
emptyNote: group ? `No group ${group}.` : "No standings available.",
|
|
4727
|
+
options: baseOptions
|
|
4728
|
+
},
|
|
4729
|
+
copy
|
|
4730
|
+
);
|
|
4731
|
+
return;
|
|
4732
|
+
}
|
|
4514
4733
|
if (target === "next") {
|
|
4515
4734
|
precheck(cfg, t);
|
|
4516
|
-
|
|
4517
|
-
const
|
|
4518
|
-
const fixture = nextFixtureForTeam(code);
|
|
4735
|
+
const code = resolveTeamArg(team, "Usage: claudinho share next <team> (or set CLAUDINHO_TEAM)");
|
|
4736
|
+
const fixture = nextFixtureForTeam(code, { from: ctx.now ?? /* @__PURE__ */ new Date() });
|
|
4519
4737
|
const matches = fixture ? [fixture] : [];
|
|
4520
4738
|
const signals2 = await reliableShareSignals(ctx, matches);
|
|
4521
4739
|
const teamName = fixture ? fixture.home.code === code ? fixture.home.name : fixture.away.name : code;
|
|
@@ -4542,17 +4760,7 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
4542
4760
|
}
|
|
4543
4761
|
if (target && target !== "today" && !isValidDate(target)) {
|
|
4544
4762
|
precheck(cfg, t);
|
|
4545
|
-
const
|
|
4546
|
-
let match = allFixtures().find((m) => m.id === target);
|
|
4547
|
-
let source2;
|
|
4548
|
-
try {
|
|
4549
|
-
if (match) {
|
|
4550
|
-
const live = await adapter.fetchByDate(match.kickoff.slice(0, 10));
|
|
4551
|
-
match = live.find((m) => m.id === target) ?? match;
|
|
4552
|
-
source2 = adapter.name;
|
|
4553
|
-
}
|
|
4554
|
-
} catch {
|
|
4555
|
-
}
|
|
4763
|
+
const { match, source: source2 } = await getMatchById(adapterFor(ctx), target);
|
|
4556
4764
|
const matches = match ? [match] : [];
|
|
4557
4765
|
const signals2 = await reliableShareSignals(ctx, matches);
|
|
4558
4766
|
emitShare(
|
|
@@ -4614,16 +4822,53 @@ var VIBES = [
|
|
|
4614
4822
|
"Stoppage time and a clean stack trace.",
|
|
4615
4823
|
"Coding into extra time."
|
|
4616
4824
|
];
|
|
4825
|
+
var VIBES_OPENER = [
|
|
4826
|
+
"Day one of the tournament. Save your work \u2014 it\u2019s about to get loud.",
|
|
4827
|
+
"Opening day: fresh bracket, fresh branch."
|
|
4828
|
+
];
|
|
4829
|
+
var VIBES_FINAL = [
|
|
4830
|
+
"Final day. One last build, one last whistle.",
|
|
4831
|
+
"Ship it before the trophy does."
|
|
4832
|
+
];
|
|
4833
|
+
function vibeLiveSegment(live, team) {
|
|
4834
|
+
const code = team?.toUpperCase();
|
|
4835
|
+
const pick2 = (code && live.find((m) => m.home.code === code || m.away.code === code)) ?? live[0];
|
|
4836
|
+
if (!pick2) return void 0;
|
|
4837
|
+
const minute = pick2.status === "HT" ? "HT" : pick2.minute ? `${pick2.minute}'` : "LIVE";
|
|
4838
|
+
return `${pick2.home.flag} ${scoreline(pick2)} ${pick2.away.flag} ${minute}`;
|
|
4839
|
+
}
|
|
4840
|
+
function vibePool(todayLocal, fixtures = allFixtures()) {
|
|
4841
|
+
let first;
|
|
4842
|
+
let last;
|
|
4843
|
+
for (const m of fixtures) {
|
|
4844
|
+
const d = m.kickoff.slice(0, 10);
|
|
4845
|
+
if (!first || d < first) first = d;
|
|
4846
|
+
if (!last || d > last) last = d;
|
|
4847
|
+
}
|
|
4848
|
+
if (todayLocal === first) return [...VIBES, ...VIBES_OPENER];
|
|
4849
|
+
if (todayLocal === last) return [...VIBES, ...VIBES_FINAL];
|
|
4850
|
+
return VIBES;
|
|
4851
|
+
}
|
|
4617
4852
|
function cmdVibe(ctx) {
|
|
4618
4853
|
const { cfg } = ctx;
|
|
4619
|
-
const
|
|
4854
|
+
const pool = vibePool(localDate((ctx.now ?? /* @__PURE__ */ new Date()).toISOString(), cfg.tz));
|
|
4855
|
+
const line2 = pool[Math.floor(Math.random() * pool.length)];
|
|
4856
|
+
let liveSeg;
|
|
4857
|
+
try {
|
|
4858
|
+
const state = readCurrentState(cfg.source, resolveCompetition());
|
|
4859
|
+
liveSeg = vibeLiveSegment(
|
|
4860
|
+
liveMatchesFromCache(state, (ctx.now ?? /* @__PURE__ */ new Date()).getTime()),
|
|
4861
|
+
process.env.CLAUDINHO_TEAM
|
|
4862
|
+
);
|
|
4863
|
+
} catch {
|
|
4864
|
+
}
|
|
4620
4865
|
if (cfg.json) {
|
|
4621
|
-
emitJson({ vibe: line2, tag: "#VibingLaVidaLoca" });
|
|
4866
|
+
emitJson({ vibe: line2, tag: "#VibingLaVidaLoca", ...liveSeg ? { live: liveSeg } : {} });
|
|
4622
4867
|
return;
|
|
4623
4868
|
}
|
|
4624
4869
|
const c = painterFor(cfg);
|
|
4625
4870
|
out();
|
|
4626
|
-
out(" \u26BD " + c.bold(line2));
|
|
4871
|
+
out(" \u26BD " + (liveSeg ? `${liveSeg} \u2014 ` : "") + c.bold(line2 ?? ""));
|
|
4627
4872
|
out(" " + c.cyan("#VibingLaVidaLoca"));
|
|
4628
4873
|
out();
|
|
4629
4874
|
}
|
|
@@ -4637,7 +4882,7 @@ function handlePipeError(stream) {
|
|
|
4637
4882
|
}
|
|
4638
4883
|
handlePipeError(process.stdout);
|
|
4639
4884
|
handlePipeError(process.stderr);
|
|
4640
|
-
var VERSION = "0.
|
|
4885
|
+
var VERSION = "0.5.0";
|
|
4641
4886
|
var DISCLAIMER = "Claudinho is an independent fan project. Not affiliated with or endorsed by FIFA or Anthropic.";
|
|
4642
4887
|
function ctxFrom(cmd) {
|
|
4643
4888
|
const root = cmd.parent ?? cmd;
|
|
@@ -4652,7 +4897,9 @@ function fail(err) {
|
|
|
4652
4897
|
process.exit(1);
|
|
4653
4898
|
}
|
|
4654
4899
|
var program = new Command();
|
|
4655
|
-
program.name("claudinho").description(
|
|
4900
|
+
program.name("claudinho").description(
|
|
4901
|
+
"The 2026 men\u2019s football tournament in your terminal, your Claude Code statusline, and any MCP client.\n" + DISCLAIMER
|
|
4902
|
+
).version(VERSION, "-v, --version").option("--lang <code>", "language: en, es, pt, fr").option("--tz <zone>", "IANA timezone, e.g. America/Mexico_City").option("--json", "output JSON (for scripting)").option("--no-color", "disable ANSI colors").option("--source <name>", "live data provider (advanced)").option("--flavor <level>", "commentary flair: off, subtle, full (default: full)").option("--no-markets", "hide prediction-market signals (informational only)");
|
|
4656
4903
|
program.addHelpText("after", "\n#VibingLaVidaLoca \u26BD");
|
|
4657
4904
|
program.command("today").description("show a day's fixtures (default: today)").argument("[date]", "date as YYYY-MM-DD").action(async (date, _opts, cmd) => {
|
|
4658
4905
|
try {
|
|
@@ -4668,7 +4915,7 @@ program.command("live").description("show matches in play right now").action(asy
|
|
|
4668
4915
|
fail(e);
|
|
4669
4916
|
}
|
|
4670
4917
|
});
|
|
4671
|
-
program.command("next").description("show a team's next fixture").argument("
|
|
4918
|
+
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) => {
|
|
4672
4919
|
try {
|
|
4673
4920
|
await cmdNext(team, ctxFrom(cmd));
|
|
4674
4921
|
} catch (e) {
|
|
@@ -4689,14 +4936,17 @@ program.command("match").description("show a single match by id").argument("<id>
|
|
|
4689
4936
|
fail(e);
|
|
4690
4937
|
}
|
|
4691
4938
|
});
|
|
4692
|
-
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" (
|
|
4939
|
+
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) => {
|
|
4693
4940
|
try {
|
|
4694
4941
|
await cmdMarkets(target, team, ctxFrom(cmd));
|
|
4695
4942
|
} catch (e) {
|
|
4696
4943
|
fail(e);
|
|
4697
4944
|
}
|
|
4698
4945
|
});
|
|
4699
|
-
program.command("share").description("print a shareable, copy-pasteable match snippet (#VibingLaVidaLoca)").argument("[target]", '"today" (default), "live", a date, a match id,
|
|
4946
|
+
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(
|
|
4947
|
+
"[team]",
|
|
4948
|
+
'team code for "next" (default: $CLAUDINHO_TEAM), or group letter for "table" (omit for all groups)'
|
|
4949
|
+
).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) => {
|
|
4700
4950
|
try {
|
|
4701
4951
|
await cmdShare(target, team, opts, ctxFrom(cmd));
|
|
4702
4952
|
} catch (e) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claudinho/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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.0"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"build": "tsup",
|