@claudinho/cli 0.4.2 → 0.4.3
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 +14 -16
- package/dist/index.js +149 -58
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -20,10 +20,11 @@ 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
|
|
23
|
+
claudinho next [TEAM] # a team's next fixture + countdown (default: $CLAUDINHO_TEAM)
|
|
24
24
|
claudinho table [GROUP] # 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
|
+
# (next prefers the team's IN-PLAY match while one is live)
|
|
27
28
|
claudinho share [target] # copy-pasteable match snippet: today | live | <date> | <id> | next <TEAM>
|
|
28
29
|
claudinho prompt # one compact status line (for statusline/tmux/Starship)
|
|
29
30
|
claudinho init-statusline # wire it into the Claude Code statusline
|
|
@@ -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
|
|
|
@@ -110,22 +112,18 @@ claudinho share 760415 # one match by id
|
|
|
110
112
|
claudinho share next MEX --copy # …and copy it straight to the clipboard
|
|
111
113
|
```
|
|
112
114
|
|
|
113
|
-
<!-- DEMO CARD: verbatim output of `claudinho share next
|
|
115
|
+
<!-- DEMO CARD: verbatim output of `claudinho share next USA --tz America/Los_Angeles`.
|
|
114
116
|
REGENERATE immediately before merging — the market block is gate-conditional
|
|
115
117
|
and the numbers drift. Never hand-edit. -->
|
|
116
118
|
```text
|
|
117
|
-
Next up for
|
|
119
|
+
Next up for United States
|
|
118
120
|
|
|
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
|
|
121
|
+
🇺🇸 United States vs Paraguay 🇵🇾
|
|
122
|
+
Jun 12 · 18:00 America/Los_Angeles
|
|
123
|
+
SoFi Stadium, Inglewood, California, USA
|
|
126
124
|
|
|
127
125
|
#VibingLaVidaLoca · Independent fan project · not affiliated with FIFA or Anthropic.
|
|
128
|
-
Try it: npx @claudinho/cli next
|
|
126
|
+
Try it: npx @claudinho/cli next USA
|
|
129
127
|
```
|
|
130
128
|
|
|
131
129
|
Snippets are **plain text** (no color codes — they paste cleanly everywhere) and
|
|
@@ -161,7 +159,7 @@ The statusline reads from a local micro-cache and **never blocks on the
|
|
|
161
159
|
network** (<150ms). When several matches are live it shows them all inline:
|
|
162
160
|
`⚽ 🇳🇴 1–1 🇫🇷 87' · 🇸🇳 1–2 🇮🇶 86'`. Customize via env:
|
|
163
161
|
|
|
164
|
-
- `CLAUDINHO_TEAM=MEX` — show only your team's match
|
|
162
|
+
- `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
163
|
- `CLAUDINHO_MAX=2` — cap how many live matches show inline (rest collapse to `+N`; default: all)
|
|
166
164
|
- `CLAUDINHO_COMPACT=0` — show 3-letter codes alongside flags
|
|
167
165
|
|
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);
|
|
@@ -2867,6 +2874,33 @@ function shiftUtcDate(dateISO, days) {
|
|
|
2867
2874
|
const [y, m, d] = dateISO.slice(0, 10).split("-").map(Number);
|
|
2868
2875
|
return new Date(Date.UTC(y ?? 1970, (m ?? 1) - 1, (d ?? 1) + days)).toISOString().slice(0, 10);
|
|
2869
2876
|
}
|
|
2877
|
+
var EXTRA_TIME_SLACK_MS = 60 * 6e4;
|
|
2878
|
+
async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Date()) {
|
|
2879
|
+
const nowMs = now.getTime();
|
|
2880
|
+
const candidate = fixturesByTeam(code).find((m) => {
|
|
2881
|
+
const k = Date.parse(m.kickoff);
|
|
2882
|
+
return nowMs >= k && nowMs <= k + LIVE_WINDOW_MS + EXTRA_TIME_SLACK_MS;
|
|
2883
|
+
});
|
|
2884
|
+
if (candidate) {
|
|
2885
|
+
const r = await getMatchById(adapter, candidate.id);
|
|
2886
|
+
const m = r.match ?? candidate;
|
|
2887
|
+
if (!isFinished(m.status)) return { ...r, match: m };
|
|
2888
|
+
}
|
|
2889
|
+
const next = nextFixtureForTeam(code, { from: now });
|
|
2890
|
+
return { match: next, degraded: false };
|
|
2891
|
+
}
|
|
2892
|
+
async function getMatchById(adapter, id) {
|
|
2893
|
+
const base = allFixtures().find((m) => m.id === id);
|
|
2894
|
+
if (!base) return { match: void 0, degraded: false };
|
|
2895
|
+
const day = base.kickoff.slice(0, 10);
|
|
2896
|
+
try {
|
|
2897
|
+
const live = adapter.fetchWindow ? await adapter.fetchWindow(shiftUtcDate(day, -1), shiftUtcDate(day, 1)) : await adapter.fetchByDate(day);
|
|
2898
|
+
const hit = live.find((m) => m.id === id);
|
|
2899
|
+
return { match: hit ?? base, degraded: false, source: hit ? adapter.name : void 0 };
|
|
2900
|
+
} catch {
|
|
2901
|
+
return { match: base, degraded: true };
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2870
2904
|
async function getLiveMatches(adapter) {
|
|
2871
2905
|
try {
|
|
2872
2906
|
return { matches: await adapter.fetchLive(), degraded: false, source: adapter.name };
|
|
@@ -2875,6 +2909,12 @@ async function getLiveMatches(adapter) {
|
|
|
2875
2909
|
}
|
|
2876
2910
|
}
|
|
2877
2911
|
var DEFAULT_MAX_AGE_MS = 15 * 6e4;
|
|
2912
|
+
function marketRelevant(match, now = /* @__PURE__ */ new Date()) {
|
|
2913
|
+
if (isLive(match.status)) return true;
|
|
2914
|
+
if (isFinished(match.status)) return false;
|
|
2915
|
+
const k = Date.parse(match.kickoff);
|
|
2916
|
+
return !Number.isFinite(k) || now.getTime() <= k + LIVE_WINDOW_MS;
|
|
2917
|
+
}
|
|
2878
2918
|
function normalizeOutcomes(outcomes) {
|
|
2879
2919
|
const sum = outcomes.reduce(
|
|
2880
2920
|
(s, o) => s + (Number.isFinite(o.probability) && o.probability > 0 ? o.probability : 0),
|
|
@@ -3812,15 +3852,10 @@ function releaseLock() {
|
|
|
3812
3852
|
}
|
|
3813
3853
|
|
|
3814
3854
|
// src/statusline.ts
|
|
3815
|
-
var LIVE_WINDOW_MS = 140 * 6e4;
|
|
3816
3855
|
var DISPLAY_STALE_MS = 5 * 6e4;
|
|
3817
3856
|
var LIVE_TTL_MS = 15e3;
|
|
3818
3857
|
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;
|
|
3858
|
+
return fixturesInLiveWindow(now, fixtures).length > 0;
|
|
3824
3859
|
}
|
|
3825
3860
|
function nextOverall(now, fixtures = allFixtures()) {
|
|
3826
3861
|
return [...fixtures].sort(byKickoff).find((m) => Date.parse(m.kickoff) >= now);
|
|
@@ -3855,6 +3890,17 @@ function renderPrompt(state, opts = {}) {
|
|
|
3855
3890
|
if (overflow > 0) line2 += ` +${overflow}`;
|
|
3856
3891
|
return line2;
|
|
3857
3892
|
}
|
|
3893
|
+
const cacheFresh = !!state && state.degraded !== true && ageMs(state, nowMs) < DISPLAY_STALE_MS;
|
|
3894
|
+
if (!cacheFresh) {
|
|
3895
|
+
const win = fixturesInLiveWindow(nowMs).filter(
|
|
3896
|
+
(m) => !team || m.home.code === team || m.away.code === team
|
|
3897
|
+
);
|
|
3898
|
+
const first = win[0];
|
|
3899
|
+
if (first) {
|
|
3900
|
+
const more = win.length - 1;
|
|
3901
|
+
return `\u26BD ${first.home.flag} vs ${first.away.flag} live \xB7 syncing\u2026` + (more > 0 ? ` +${more}` : "");
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3858
3904
|
const next = team ? nextFixtureForTeam(team, { from: now }) : nextOverall(nowMs);
|
|
3859
3905
|
if (next) {
|
|
3860
3906
|
return `${next.home.flag} vs ${next.away.flag} in ${countdown(next.kickoff, now)}`;
|
|
@@ -4070,8 +4116,10 @@ async function marketSignalsFor(ctx, matches, opts = {}) {
|
|
|
4070
4116
|
}
|
|
4071
4117
|
async function reliableMarketSignals(ctx, matches) {
|
|
4072
4118
|
if (ctx.cfg.markets === false) return /* @__PURE__ */ new Map();
|
|
4073
|
-
const
|
|
4074
|
-
const
|
|
4119
|
+
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
4120
|
+
const relevant = matches.filter((m) => marketRelevant(m, now));
|
|
4121
|
+
if (relevant.length === 0) return /* @__PURE__ */ new Map();
|
|
4122
|
+
const raw = await marketSignalsFor(ctx, relevant, DEFAULT_ON_MARKET_OPTS);
|
|
4075
4123
|
const out2 = /* @__PURE__ */ new Map();
|
|
4076
4124
|
for (const [id, s] of raw) if (isReliableMarketSignal(s, { now })) out2.set(id, s);
|
|
4077
4125
|
return out2;
|
|
@@ -4094,10 +4142,22 @@ function precheck(cfg, t, date) {
|
|
|
4094
4142
|
if (cfg.tz && !isValidTimeZone(cfg.tz)) {
|
|
4095
4143
|
process.stderr.write(t("warn.tz", { tz: cfg.tz }) + "\n");
|
|
4096
4144
|
}
|
|
4145
|
+
const competition = resolveCompetition();
|
|
4146
|
+
if (competition !== DEFAULT_COMPETITION) {
|
|
4147
|
+
process.stderr.write(
|
|
4148
|
+
`claudinho: CLAUDINHO_COMPETITION=${competition} \u2014 live data follows a different competition than the bundled 2026 schedule.
|
|
4149
|
+
`
|
|
4150
|
+
);
|
|
4151
|
+
}
|
|
4097
4152
|
if (date !== void 0 && !isValidDate(date)) {
|
|
4098
4153
|
throw new InputError(t("err.date", { date }));
|
|
4099
4154
|
}
|
|
4100
4155
|
}
|
|
4156
|
+
function resolveTeamArg(team, usage) {
|
|
4157
|
+
const code = team ?? process.env.CLAUDINHO_TEAM;
|
|
4158
|
+
if (!code) throw new InputError(usage);
|
|
4159
|
+
return code.toUpperCase();
|
|
4160
|
+
}
|
|
4101
4161
|
async function cmdToday(date, ctx) {
|
|
4102
4162
|
const { cfg, t } = ctx;
|
|
4103
4163
|
precheck(cfg, t, date);
|
|
@@ -4160,7 +4220,7 @@ async function cmdLive(ctx) {
|
|
|
4160
4220
|
}
|
|
4161
4221
|
async function cmdNext(team, { cfg, t }) {
|
|
4162
4222
|
precheck(cfg, t);
|
|
4163
|
-
const code = team
|
|
4223
|
+
const code = resolveTeamArg(team, "Usage: claudinho next <team> (or set CLAUDINHO_TEAM)");
|
|
4164
4224
|
const fixture = nextFixtureForTeam(code);
|
|
4165
4225
|
if (cfg.json) {
|
|
4166
4226
|
emitJson({ team: code, fixture: fixture ?? null });
|
|
@@ -4298,20 +4358,15 @@ function cmdInitHook(opts, { cfg }) {
|
|
|
4298
4358
|
async function cmdMatch(id, ctx) {
|
|
4299
4359
|
const { cfg, t } = ctx;
|
|
4300
4360
|
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
|
-
}
|
|
4361
|
+
const { match, degraded, source: liveSource } = await getMatchById(adapterFor(ctx), id);
|
|
4312
4362
|
const marketSignal = match ? await reliableMarketSignalFor(ctx, match) : void 0;
|
|
4313
4363
|
if (cfg.json) {
|
|
4314
|
-
emitJson({
|
|
4364
|
+
emitJson({
|
|
4365
|
+
degraded,
|
|
4366
|
+
match: match ?? null,
|
|
4367
|
+
source: liveSource ?? null,
|
|
4368
|
+
marketSignal: marketSignal ?? null
|
|
4369
|
+
});
|
|
4315
4370
|
return;
|
|
4316
4371
|
}
|
|
4317
4372
|
const c = painterFor(cfg);
|
|
@@ -4351,8 +4406,13 @@ var MARKET_INFO = "Prediction-market data is informational only.";
|
|
|
4351
4406
|
function marketDisplayable(sig) {
|
|
4352
4407
|
return !sig.ambiguous && sig.favorite != null && hasSaneDistribution(sig.outcomes);
|
|
4353
4408
|
}
|
|
4354
|
-
function marketHeaderLine(m) {
|
|
4355
|
-
|
|
4409
|
+
function marketHeaderLine(m, cfg) {
|
|
4410
|
+
const when = formatDate(m.kickoff, { tz: cfg.tz, locale: cfg.lang });
|
|
4411
|
+
return `${m.home.flag} ${m.home.name} vs ${m.away.name} ${m.away.flag} \xB7 ${when}`;
|
|
4412
|
+
}
|
|
4413
|
+
function noSignalLine(m, now) {
|
|
4414
|
+
if (marketRelevant(m, now)) return "No market signal for this match.";
|
|
4415
|
+
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
4416
|
}
|
|
4357
4417
|
function printMarketBlock(m, sig, c) {
|
|
4358
4418
|
for (const line2 of marketBlock(sig, m)) out(" " + c.dim(line2));
|
|
@@ -4361,10 +4421,10 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
4361
4421
|
const { cfg, t } = ctx;
|
|
4362
4422
|
if (target === "next") {
|
|
4363
4423
|
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;
|
|
4424
|
+
const code = resolveTeamArg(team, "Usage: claudinho markets next <team> (or set CLAUDINHO_TEAM)");
|
|
4425
|
+
const now2 = ctx.now ?? /* @__PURE__ */ new Date();
|
|
4426
|
+
const { match: fixture } = await marketFixtureForTeam(adapterFor(ctx), code, now2);
|
|
4427
|
+
const sig = fixture && marketRelevant(fixture, now2) ? (await marketSignalsFor(ctx, [fixture], MARKETS_CMD_OPTS)).get(fixture.id) : void 0;
|
|
4368
4428
|
const shown = sig && marketDisplayable(sig) ? sig : void 0;
|
|
4369
4429
|
if (cfg.json) {
|
|
4370
4430
|
emitJson({
|
|
@@ -4380,10 +4440,10 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
4380
4440
|
if (!fixture) {
|
|
4381
4441
|
out(c2.dim(" " + t("next.none", { team: code })));
|
|
4382
4442
|
} else {
|
|
4383
|
-
out(header(marketHeaderLine(fixture), c2));
|
|
4443
|
+
out(header(marketHeaderLine(fixture, cfg), c2));
|
|
4384
4444
|
out();
|
|
4385
4445
|
if (shown) printMarketBlock(fixture, shown, c2);
|
|
4386
|
-
else out(c2.dim("
|
|
4446
|
+
else out(c2.dim(" " + noSignalLine(fixture, now2)));
|
|
4387
4447
|
}
|
|
4388
4448
|
out();
|
|
4389
4449
|
out(disclaimer(t, c2));
|
|
@@ -4392,8 +4452,9 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
4392
4452
|
}
|
|
4393
4453
|
if (target && target !== "today" && !isValidDate(target)) {
|
|
4394
4454
|
precheck(cfg, t);
|
|
4395
|
-
const
|
|
4396
|
-
const
|
|
4455
|
+
const now2 = ctx.now ?? /* @__PURE__ */ new Date();
|
|
4456
|
+
const { match } = await getMatchById(adapterFor(ctx), target);
|
|
4457
|
+
const sig = match && marketRelevant(match, now2) ? (await marketSignalsFor(ctx, [match], MARKETS_CMD_OPTS)).get(match.id) : void 0;
|
|
4397
4458
|
const shown = sig && marketDisplayable(sig) ? sig : void 0;
|
|
4398
4459
|
if (cfg.json) {
|
|
4399
4460
|
emitJson({ matchId: target, informationalOnly: true, signal: shown ?? null });
|
|
@@ -4404,10 +4465,10 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
4404
4465
|
if (!match) {
|
|
4405
4466
|
out(c2.dim(" " + t("match.none", { id: target })));
|
|
4406
4467
|
} else {
|
|
4407
|
-
out(header(marketHeaderLine(match), c2));
|
|
4468
|
+
out(header(marketHeaderLine(match, cfg), c2));
|
|
4408
4469
|
out();
|
|
4409
4470
|
if (shown) printMarketBlock(match, shown, c2);
|
|
4410
|
-
else out(c2.dim("
|
|
4471
|
+
else out(c2.dim(" " + noSignalLine(match, now2)));
|
|
4411
4472
|
}
|
|
4412
4473
|
out();
|
|
4413
4474
|
out(disclaimer(t, c2));
|
|
@@ -4416,11 +4477,13 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
4416
4477
|
}
|
|
4417
4478
|
const explicitDate = target && target !== "today" ? target : void 0;
|
|
4418
4479
|
precheck(cfg, t, explicitDate);
|
|
4419
|
-
const
|
|
4480
|
+
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
4481
|
+
const date = explicitDate ?? localDate(now.toISOString(), cfg.tz);
|
|
4420
4482
|
const { matches } = await getMatchesForDate(adapterFor(ctx), date);
|
|
4421
4483
|
const todays = fixturesByDate(date, matches, cfg.tz);
|
|
4422
|
-
const
|
|
4423
|
-
const
|
|
4484
|
+
const relevant = todays.filter((m) => marketRelevant(m, now));
|
|
4485
|
+
const signals = await marketSignalsFor(ctx, relevant, MARKETS_CMD_OPTS);
|
|
4486
|
+
const rows = relevant.map((m) => ({ match: m, signal: signals.get(m.id) })).filter(
|
|
4424
4487
|
(r) => !!r.signal && marketDisplayable(r.signal)
|
|
4425
4488
|
);
|
|
4426
4489
|
if (cfg.json) {
|
|
@@ -4437,7 +4500,7 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
4437
4500
|
out(c.dim(` No market signals available for ${date}.`));
|
|
4438
4501
|
} else {
|
|
4439
4502
|
for (const { match, signal } of rows) {
|
|
4440
|
-
out(" " + c.bold(marketHeaderLine(match)));
|
|
4503
|
+
out(" " + c.bold(marketHeaderLine(match, cfg)));
|
|
4441
4504
|
printMarketBlock(match, signal, c);
|
|
4442
4505
|
out();
|
|
4443
4506
|
}
|
|
@@ -4513,8 +4576,7 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
4513
4576
|
}
|
|
4514
4577
|
if (target === "next") {
|
|
4515
4578
|
precheck(cfg, t);
|
|
4516
|
-
|
|
4517
|
-
const code = team.toUpperCase();
|
|
4579
|
+
const code = resolveTeamArg(team, "Usage: claudinho share next <team> (or set CLAUDINHO_TEAM)");
|
|
4518
4580
|
const fixture = nextFixtureForTeam(code);
|
|
4519
4581
|
const matches = fixture ? [fixture] : [];
|
|
4520
4582
|
const signals2 = await reliableShareSignals(ctx, matches);
|
|
@@ -4542,17 +4604,7 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
4542
4604
|
}
|
|
4543
4605
|
if (target && target !== "today" && !isValidDate(target)) {
|
|
4544
4606
|
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
|
-
}
|
|
4607
|
+
const { match, source: source2 } = await getMatchById(adapterFor(ctx), target);
|
|
4556
4608
|
const matches = match ? [match] : [];
|
|
4557
4609
|
const signals2 = await reliableShareSignals(ctx, matches);
|
|
4558
4610
|
emitShare(
|
|
@@ -4614,16 +4666,53 @@ var VIBES = [
|
|
|
4614
4666
|
"Stoppage time and a clean stack trace.",
|
|
4615
4667
|
"Coding into extra time."
|
|
4616
4668
|
];
|
|
4669
|
+
var VIBES_OPENER = [
|
|
4670
|
+
"Day one of the tournament. Save your work \u2014 it\u2019s about to get loud.",
|
|
4671
|
+
"Opening day: fresh bracket, fresh branch."
|
|
4672
|
+
];
|
|
4673
|
+
var VIBES_FINAL = [
|
|
4674
|
+
"Final day. One last build, one last whistle.",
|
|
4675
|
+
"Ship it before the trophy does."
|
|
4676
|
+
];
|
|
4677
|
+
function vibeLiveSegment(live, team) {
|
|
4678
|
+
const code = team?.toUpperCase();
|
|
4679
|
+
const pick2 = (code && live.find((m) => m.home.code === code || m.away.code === code)) ?? live[0];
|
|
4680
|
+
if (!pick2) return void 0;
|
|
4681
|
+
const minute = pick2.status === "HT" ? "HT" : pick2.minute ? `${pick2.minute}'` : "LIVE";
|
|
4682
|
+
return `${pick2.home.flag} ${scoreline(pick2)} ${pick2.away.flag} ${minute}`;
|
|
4683
|
+
}
|
|
4684
|
+
function vibePool(todayLocal, fixtures = allFixtures()) {
|
|
4685
|
+
let first;
|
|
4686
|
+
let last;
|
|
4687
|
+
for (const m of fixtures) {
|
|
4688
|
+
const d = m.kickoff.slice(0, 10);
|
|
4689
|
+
if (!first || d < first) first = d;
|
|
4690
|
+
if (!last || d > last) last = d;
|
|
4691
|
+
}
|
|
4692
|
+
if (todayLocal === first) return [...VIBES, ...VIBES_OPENER];
|
|
4693
|
+
if (todayLocal === last) return [...VIBES, ...VIBES_FINAL];
|
|
4694
|
+
return VIBES;
|
|
4695
|
+
}
|
|
4617
4696
|
function cmdVibe(ctx) {
|
|
4618
4697
|
const { cfg } = ctx;
|
|
4619
|
-
const
|
|
4698
|
+
const pool = vibePool(localDate((ctx.now ?? /* @__PURE__ */ new Date()).toISOString(), cfg.tz));
|
|
4699
|
+
const line2 = pool[Math.floor(Math.random() * pool.length)];
|
|
4700
|
+
let liveSeg;
|
|
4701
|
+
try {
|
|
4702
|
+
const state = readCurrentState(cfg.source, resolveCompetition());
|
|
4703
|
+
liveSeg = vibeLiveSegment(
|
|
4704
|
+
liveMatchesFromCache(state, (ctx.now ?? /* @__PURE__ */ new Date()).getTime()),
|
|
4705
|
+
process.env.CLAUDINHO_TEAM
|
|
4706
|
+
);
|
|
4707
|
+
} catch {
|
|
4708
|
+
}
|
|
4620
4709
|
if (cfg.json) {
|
|
4621
|
-
emitJson({ vibe: line2, tag: "#VibingLaVidaLoca" });
|
|
4710
|
+
emitJson({ vibe: line2, tag: "#VibingLaVidaLoca", ...liveSeg ? { live: liveSeg } : {} });
|
|
4622
4711
|
return;
|
|
4623
4712
|
}
|
|
4624
4713
|
const c = painterFor(cfg);
|
|
4625
4714
|
out();
|
|
4626
|
-
out(" \u26BD " + c.bold(line2));
|
|
4715
|
+
out(" \u26BD " + (liveSeg ? `${liveSeg} \u2014 ` : "") + c.bold(line2 ?? ""));
|
|
4627
4716
|
out(" " + c.cyan("#VibingLaVidaLoca"));
|
|
4628
4717
|
out();
|
|
4629
4718
|
}
|
|
@@ -4637,7 +4726,7 @@ function handlePipeError(stream) {
|
|
|
4637
4726
|
}
|
|
4638
4727
|
handlePipeError(process.stdout);
|
|
4639
4728
|
handlePipeError(process.stderr);
|
|
4640
|
-
var VERSION = "0.4.
|
|
4729
|
+
var VERSION = "0.4.3";
|
|
4641
4730
|
var DISCLAIMER = "Claudinho is an independent fan project. Not affiliated with or endorsed by FIFA or Anthropic.";
|
|
4642
4731
|
function ctxFrom(cmd) {
|
|
4643
4732
|
const root = cmd.parent ?? cmd;
|
|
@@ -4652,7 +4741,9 @@ function fail(err) {
|
|
|
4652
4741
|
process.exit(1);
|
|
4653
4742
|
}
|
|
4654
4743
|
var program = new Command();
|
|
4655
|
-
program.name("claudinho").description(
|
|
4744
|
+
program.name("claudinho").description(
|
|
4745
|
+
"The 2026 men\u2019s football tournament in your terminal, your Claude Code statusline, and any MCP client.\n" + DISCLAIMER
|
|
4746
|
+
).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
4747
|
program.addHelpText("after", "\n#VibingLaVidaLoca \u26BD");
|
|
4657
4748
|
program.command("today").description("show a day's fixtures (default: today)").argument("[date]", "date as YYYY-MM-DD").action(async (date, _opts, cmd) => {
|
|
4658
4749
|
try {
|
|
@@ -4668,7 +4759,7 @@ program.command("live").description("show matches in play right now").action(asy
|
|
|
4668
4759
|
fail(e);
|
|
4669
4760
|
}
|
|
4670
4761
|
});
|
|
4671
|
-
program.command("next").description("show a team's next fixture").argument("
|
|
4762
|
+
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
4763
|
try {
|
|
4673
4764
|
await cmdNext(team, ctxFrom(cmd));
|
|
4674
4765
|
} catch (e) {
|
|
@@ -4689,14 +4780,14 @@ program.command("match").description("show a single match by id").argument("<id>
|
|
|
4689
4780
|
fail(e);
|
|
4690
4781
|
}
|
|
4691
4782
|
});
|
|
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" (
|
|
4783
|
+
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
4784
|
try {
|
|
4694
4785
|
await cmdMarkets(target, team, ctxFrom(cmd));
|
|
4695
4786
|
} catch (e) {
|
|
4696
4787
|
fail(e);
|
|
4697
4788
|
}
|
|
4698
4789
|
});
|
|
4699
|
-
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" (
|
|
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) => {
|
|
4700
4791
|
try {
|
|
4701
4792
|
await cmdShare(target, team, opts, ctxFrom(cmd));
|
|
4702
4793
|
} catch (e) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claudinho/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
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.
|
|
59
|
+
"@claudinho/core": "0.4.3"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"build": "tsup",
|