@claudinho/cli 0.8.18 โ 0.9.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 +1 -1
- package/dist/index.js +495 -210
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -186,7 +186,7 @@ The statusline reads from a local micro-cache and **never blocks on the
|
|
|
186
186
|
network** (<150ms). When several matches are live it shows them all inline:
|
|
187
187
|
`โฝ ๐ณ๐ด 1โ1 ๐ซ๐ท 87' ยท ๐ธ๐ณ 1โ2 ๐ฎ๐ถ 86'`. Customize via env:
|
|
188
188
|
|
|
189
|
-
- `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
|
|
189
|
+
- `CLAUDINHO_TEAM=MEX` โ show only your team's match (a nation name works too, e.g. `CLAUDINHO_TEAM=mexico`); also the default team for `next`, `markets next`, and `share next` when the argument is omitted
|
|
190
190
|
- `CLAUDINHO_MAX=2` โ cap how many live matches show inline (rest collapse to `+N`; default: all)
|
|
191
191
|
- `CLAUDINHO_COMPACT=0` โ show 3-letter codes alongside flags
|
|
192
192
|
- `CLAUDINHO_FLAGS=off` โ drop emoji flags for 3-letter codes (statusline) / plain names (`today`, `live`, `table`, `next`, hook); already automatic on terminals that can't render flag emoji, e.g. Warp
|
package/dist/index.js
CHANGED
|
@@ -381,7 +381,8 @@ function normalizeLang(lang) {
|
|
|
381
381
|
}
|
|
382
382
|
function t(lang, key, vars) {
|
|
383
383
|
const dict = CATALOGS[normalizeLang(lang)];
|
|
384
|
-
|
|
384
|
+
const en = EN;
|
|
385
|
+
let s = dict[key] ?? en[key] ?? key;
|
|
385
386
|
if (vars) {
|
|
386
387
|
for (const [k, v] of Object.entries(vars)) s = s.replaceAll(`{${k}}`, v);
|
|
387
388
|
}
|
|
@@ -500,6 +501,63 @@ function shiftUtcDate(dateISO, days) {
|
|
|
500
501
|
const [y, m, d] = dateISO.slice(0, 10).split("-").map(Number);
|
|
501
502
|
return new Date(Date.UTC(y ?? 1970, (m ?? 1) - 1, (d ?? 1) + days)).toISOString().slice(0, 10);
|
|
502
503
|
}
|
|
504
|
+
var FEED_TEXT_MAX = 100;
|
|
505
|
+
function sanitizeFeedText(value, max = FEED_TEXT_MAX) {
|
|
506
|
+
let out2 = "";
|
|
507
|
+
let count = 0;
|
|
508
|
+
for (const ch of String(value)) {
|
|
509
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
510
|
+
const isWhitespaceControl = cp === 9 || cp === 10 || cp === 13;
|
|
511
|
+
if ((cp <= 31 || cp >= 127 && cp <= 159) && !isWhitespaceControl) continue;
|
|
512
|
+
if (count >= max) break;
|
|
513
|
+
out2 += isWhitespaceControl ? " " : ch;
|
|
514
|
+
count++;
|
|
515
|
+
}
|
|
516
|
+
return out2;
|
|
517
|
+
}
|
|
518
|
+
function sanitizeTeam(t2) {
|
|
519
|
+
return {
|
|
520
|
+
...t2 ?? {},
|
|
521
|
+
code: sanitizeFeedText(t2?.code ?? ""),
|
|
522
|
+
name: sanitizeFeedText(t2?.name ?? ""),
|
|
523
|
+
flag: sanitizeFeedText(t2?.flag ?? "")
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function finiteOrUndefined(v) {
|
|
527
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
528
|
+
}
|
|
529
|
+
function sanitizeScorePair(v) {
|
|
530
|
+
const home = finiteOrUndefined(v?.home);
|
|
531
|
+
const away = finiteOrUndefined(v?.away);
|
|
532
|
+
return home !== void 0 && away !== void 0 ? { home, away } : void 0;
|
|
533
|
+
}
|
|
534
|
+
function sanitizeMatchStrings(m) {
|
|
535
|
+
const score = sanitizeScorePair(m.score);
|
|
536
|
+
return {
|
|
537
|
+
...m,
|
|
538
|
+
venue: sanitizeFeedText(m.venue ?? ""),
|
|
539
|
+
city: m.city == null ? m.city : sanitizeFeedText(m.city),
|
|
540
|
+
country: m.country == null ? m.country : sanitizeFeedText(m.country),
|
|
541
|
+
home: sanitizeTeam(m.home),
|
|
542
|
+
away: sanitizeTeam(m.away),
|
|
543
|
+
score,
|
|
544
|
+
shootout: score ? sanitizeScorePair(m.shootout) : void 0,
|
|
545
|
+
minute: finiteOrUndefined(m.minute)
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
var segmenter = new Intl.Segmenter();
|
|
549
|
+
var WIDE_CLUSTER = new RegExp("^(?:\\p{Regional_Indicator}|\\p{Extended_Pictographic})", "u");
|
|
550
|
+
function displayWidth(s) {
|
|
551
|
+
let w = 0;
|
|
552
|
+
for (const { segment } of segmenter.segment(s)) {
|
|
553
|
+
w += WIDE_CLUSTER.test(segment) ? 2 : 1;
|
|
554
|
+
}
|
|
555
|
+
return w;
|
|
556
|
+
}
|
|
557
|
+
function padVisible(s, width) {
|
|
558
|
+
const w = displayWidth(s);
|
|
559
|
+
return w >= width ? s : s + " ".repeat(width - w);
|
|
560
|
+
}
|
|
503
561
|
function outcomeFromScore(home, away) {
|
|
504
562
|
if (home > away) return "H";
|
|
505
563
|
if (home < away) return "A";
|
|
@@ -2886,10 +2944,27 @@ function rosterAtZero(matches) {
|
|
|
2886
2944
|
var ESPN_SOCCER = "https://site.api.espn.com/apis/site/v2/sports/soccer";
|
|
2887
2945
|
var DEFAULT_COMPETITION = "fifa.world";
|
|
2888
2946
|
var DEFAULT_BASE = `${ESPN_SOCCER}/${DEFAULT_COMPETITION}`;
|
|
2889
|
-
var USER_AGENT = "
|
|
2947
|
+
var USER_AGENT = `claudinho/${"0.9.0"} (+https://github.com/arturogarrido/claudinho)`;
|
|
2948
|
+
var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
2890
2949
|
function competitionBase(slug) {
|
|
2891
2950
|
return `${ESPN_SOCCER}/${slug}`;
|
|
2892
2951
|
}
|
|
2952
|
+
var DEFAULT_TIMEOUT_MS = 6e3;
|
|
2953
|
+
var STANDINGS_SHARE_MS = 3e4;
|
|
2954
|
+
var ProviderError = class extends Error {
|
|
2955
|
+
kind;
|
|
2956
|
+
status;
|
|
2957
|
+
constructor(message, kind, status) {
|
|
2958
|
+
super(message);
|
|
2959
|
+
this.name = "ProviderError";
|
|
2960
|
+
this.kind = kind;
|
|
2961
|
+
this.status = status;
|
|
2962
|
+
}
|
|
2963
|
+
/** 429/403 โ the upstream is refusing us; retrying at the live cadence makes it worse. */
|
|
2964
|
+
get throttled() {
|
|
2965
|
+
return this.kind === "http" && (this.status === 429 || this.status === 403);
|
|
2966
|
+
}
|
|
2967
|
+
};
|
|
2893
2968
|
function mapStatus(st) {
|
|
2894
2969
|
const name = (st?.type?.name ?? "").toUpperCase();
|
|
2895
2970
|
const state = st?.type?.state ?? "";
|
|
@@ -2930,9 +3005,15 @@ function toInt(s) {
|
|
|
2930
3005
|
return Number.isFinite(n) ? n : void 0;
|
|
2931
3006
|
}
|
|
2932
3007
|
function toTeam(t2) {
|
|
2933
|
-
const name =
|
|
2934
|
-
|
|
2935
|
-
|
|
3008
|
+
const name = sanitizeFeedText(
|
|
3009
|
+
t2?.displayName ?? t2?.name ?? t2?.location ?? t2?.shortDisplayName ?? "TBD"
|
|
3010
|
+
);
|
|
3011
|
+
const code = sanitizeFeedText(t2?.abbreviation ?? name.slice(0, 3)).toUpperCase();
|
|
3012
|
+
return {
|
|
3013
|
+
code,
|
|
3014
|
+
name,
|
|
3015
|
+
flag: nationToFlag(sanitizeFeedText(t2?.displayName ?? t2?.abbreviation ?? name))
|
|
3016
|
+
};
|
|
2936
3017
|
}
|
|
2937
3018
|
function mapEspnEvent(ev, ctx = {}) {
|
|
2938
3019
|
const comp = ev.competitions?.[0];
|
|
@@ -2963,9 +3044,9 @@ function mapEspnEvent(ev, ctx = {}) {
|
|
|
2963
3044
|
stage,
|
|
2964
3045
|
group,
|
|
2965
3046
|
kickoff: ev.date,
|
|
2966
|
-
venue: comp?.venue?.fullName ?? "",
|
|
2967
|
-
city: comp?.venue?.address?.city || void 0,
|
|
2968
|
-
country: comp?.venue?.address?.country || void 0,
|
|
3047
|
+
venue: sanitizeFeedText(comp?.venue?.fullName ?? ""),
|
|
3048
|
+
city: sanitizeFeedText(comp?.venue?.address?.city ?? "") || void 0,
|
|
3049
|
+
country: sanitizeFeedText(comp?.venue?.address?.country ?? "") || void 0,
|
|
2969
3050
|
home,
|
|
2970
3051
|
away,
|
|
2971
3052
|
score: hasScore ? { home: hs, away: as } : void 0,
|
|
@@ -3025,6 +3106,20 @@ var EspnAdapter = class {
|
|
|
3025
3106
|
capabilities = { push: false, latencyHintSec: 45 };
|
|
3026
3107
|
/** Cached team-code -> group-letter map (built lazily from standings). */
|
|
3027
3108
|
groupMap;
|
|
3109
|
+
/**
|
|
3110
|
+
* One in-flight/recent standings fetch shared by fetchStandings and
|
|
3111
|
+
* fetchGroupMap, so a command like `bracket` hits the endpoint once instead
|
|
3112
|
+
* of twice, and a long-lived MCP server stays fresh (short TTL). A rejected
|
|
3113
|
+
* fetch clears the slot โ a transient failure is never cached.
|
|
3114
|
+
*/
|
|
3115
|
+
standingsShared;
|
|
3116
|
+
/**
|
|
3117
|
+
* The most recent request failure (best-effort under concurrency; cleared
|
|
3118
|
+
* when a new request starts). A post-hoc hint for callers whose result path
|
|
3119
|
+
* fails closed to `degraded` booleans but who still need to distinguish a
|
|
3120
|
+
* throttle (persist a backoff) from an ordinary blip.
|
|
3121
|
+
*/
|
|
3122
|
+
lastError;
|
|
3028
3123
|
async fetchByDate(dateISO) {
|
|
3029
3124
|
return this.fetchScoreboard(toEspnDate(dateISO));
|
|
3030
3125
|
}
|
|
@@ -3046,37 +3141,58 @@ var EspnAdapter = class {
|
|
|
3046
3141
|
const base = this.opts.baseUrl ?? DEFAULT_BASE;
|
|
3047
3142
|
return `${base.replace("/apis/site/v2/", "/apis/v2/")}/standings`;
|
|
3048
3143
|
}
|
|
3144
|
+
/** The shared standings fetch (see {@link standingsShared}). */
|
|
3145
|
+
sharedStandings() {
|
|
3146
|
+
const now = Date.now();
|
|
3147
|
+
if (this.standingsShared && now - this.standingsShared.at < STANDINGS_SHARE_MS) {
|
|
3148
|
+
return this.standingsShared.promise;
|
|
3149
|
+
}
|
|
3150
|
+
const promise = this.get(this.standingsUrl()).then(
|
|
3151
|
+
(d) => parseStandings(d)
|
|
3152
|
+
);
|
|
3153
|
+
this.standingsShared = { at: now, promise };
|
|
3154
|
+
promise.catch(() => {
|
|
3155
|
+
if (this.standingsShared?.promise === promise) this.standingsShared = void 0;
|
|
3156
|
+
});
|
|
3157
|
+
return promise;
|
|
3158
|
+
}
|
|
3049
3159
|
/**
|
|
3050
3160
|
* Authoritative, cumulative group tables from the standings endpoint. Throws
|
|
3051
3161
|
* on fetch/parse failure (the caller decides the fallback). Group-stage only:
|
|
3052
3162
|
* non-group `children` are filtered out by {@link parseStandings}.
|
|
3053
3163
|
*/
|
|
3054
3164
|
async fetchStandings() {
|
|
3055
|
-
return
|
|
3165
|
+
return this.sharedStandings();
|
|
3056
3166
|
}
|
|
3057
3167
|
/**
|
|
3058
3168
|
* Build (and cache) a team-code -> group-letter map from the standings
|
|
3059
|
-
* endpoint. Best-effort: returns {} if standings are unavailable
|
|
3060
|
-
*
|
|
3169
|
+
* endpoint. Best-effort: returns {} if standings are unavailable โ but a
|
|
3170
|
+
* transient failure is NOT cached (only a successful parse pins the map), so
|
|
3171
|
+
* one blip can't silently drop group letters for the adapter's lifetime.
|
|
3172
|
+
* Reuses the same parse/fetch as {@link fetchStandings}, so the two never
|
|
3173
|
+
* drift and one command never fetches standings twice.
|
|
3061
3174
|
*/
|
|
3062
3175
|
async fetchGroupMap(force = false) {
|
|
3063
3176
|
if (this.groupMap && !force) return this.groupMap;
|
|
3064
|
-
const map = {};
|
|
3065
3177
|
try {
|
|
3066
|
-
const tables =
|
|
3178
|
+
const tables = await this.sharedStandings();
|
|
3179
|
+
const map = {};
|
|
3067
3180
|
for (const t2 of tables) for (const r of t2.rows) map[r.team.code] = t2.group;
|
|
3181
|
+
this.groupMap = map;
|
|
3182
|
+
return map;
|
|
3068
3183
|
} catch {
|
|
3184
|
+
return {};
|
|
3069
3185
|
}
|
|
3070
|
-
this.groupMap = map;
|
|
3071
|
-
return map;
|
|
3072
3186
|
}
|
|
3073
3187
|
async fetchScoreboard(dates) {
|
|
3074
3188
|
const base = this.opts.baseUrl ?? DEFAULT_BASE;
|
|
3075
3189
|
const url = new URL(`${base}/scoreboard`);
|
|
3076
3190
|
url.searchParams.set("limit", "300");
|
|
3077
3191
|
if (dates) url.searchParams.set("dates", dates);
|
|
3078
|
-
const groupByTeam =
|
|
3079
|
-
|
|
3192
|
+
const [groupByTeam, data] = await Promise.all([
|
|
3193
|
+
this.opts.enrichGroups === false ? Promise.resolve({}) : this.fetchGroupMap(),
|
|
3194
|
+
this.get(url.toString())
|
|
3195
|
+
]);
|
|
3080
3196
|
return (data.events ?? []).map((ev) => mapEspnEvent(ev, { groupByTeam }));
|
|
3081
3197
|
}
|
|
3082
3198
|
async get(url) {
|
|
@@ -3084,17 +3200,51 @@ var EspnAdapter = class {
|
|
|
3084
3200
|
const controller = new AbortController();
|
|
3085
3201
|
const timer = setTimeout(
|
|
3086
3202
|
() => controller.abort(),
|
|
3087
|
-
this.opts.timeoutMs ??
|
|
3203
|
+
this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
3088
3204
|
);
|
|
3205
|
+
this.lastError = void 0;
|
|
3089
3206
|
try {
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3207
|
+
let res;
|
|
3208
|
+
try {
|
|
3209
|
+
res = await doFetch(url, {
|
|
3210
|
+
signal: controller.signal,
|
|
3211
|
+
// ESPN never legitimately redirects; following one would sidestep the
|
|
3212
|
+
// fixed-host guarantee, so treat any redirect as a failure (degraded).
|
|
3213
|
+
redirect: "error",
|
|
3214
|
+
headers: { Accept: "application/json", "User-Agent": USER_AGENT }
|
|
3215
|
+
});
|
|
3216
|
+
} catch (e) {
|
|
3217
|
+
throw e?.name === "AbortError" ? new ProviderError(`ESPN request timed out: ${url}`, "timeout") : new ProviderError(`ESPN request failed: ${e?.message ?? e}`, "http");
|
|
3218
|
+
}
|
|
3094
3219
|
if (!res.ok) {
|
|
3095
|
-
throw new
|
|
3220
|
+
throw new ProviderError(
|
|
3221
|
+
`ESPN request failed: ${res.status} ${res.statusText}`,
|
|
3222
|
+
"http",
|
|
3223
|
+
res.status
|
|
3224
|
+
);
|
|
3225
|
+
}
|
|
3226
|
+
const length = Number(res.headers?.get?.("content-length"));
|
|
3227
|
+
if (Number.isFinite(length) && length > MAX_RESPONSE_BYTES) {
|
|
3228
|
+
throw new ProviderError(`ESPN response too large: ${length} bytes`, "parse");
|
|
3096
3229
|
}
|
|
3097
|
-
|
|
3230
|
+
try {
|
|
3231
|
+
return await res.json();
|
|
3232
|
+
} catch (e) {
|
|
3233
|
+
throw new ProviderError(
|
|
3234
|
+
`ESPN response unparseable: ${e?.message ?? e}`,
|
|
3235
|
+
"parse"
|
|
3236
|
+
);
|
|
3237
|
+
}
|
|
3238
|
+
} catch (e) {
|
|
3239
|
+
const pe = e instanceof ProviderError ? e : new ProviderError(String(e?.message ?? e), "http");
|
|
3240
|
+
this.lastError = pe;
|
|
3241
|
+
if (typeof process !== "undefined" && process.env?.CLAUDINHO_DEBUG) {
|
|
3242
|
+
process.stderr.write(
|
|
3243
|
+
`claudinho: espn ${pe.kind}${pe.status ? ` ${pe.status}` : ""}: ${url}
|
|
3244
|
+
`
|
|
3245
|
+
);
|
|
3246
|
+
}
|
|
3247
|
+
throw pe;
|
|
3098
3248
|
} finally {
|
|
3099
3249
|
clearTimeout(timer);
|
|
3100
3250
|
}
|
|
@@ -3818,13 +3968,18 @@ function resolveCompetition(explicit) {
|
|
|
3818
3968
|
}
|
|
3819
3969
|
return DEFAULT_COMPETITION;
|
|
3820
3970
|
}
|
|
3971
|
+
var KNOWN_SOURCES = ["espn"];
|
|
3821
3972
|
function makeAdapter(source = "espn") {
|
|
3822
3973
|
switch (source) {
|
|
3823
|
-
|
|
3974
|
+
case "espn": {
|
|
3824
3975
|
const competition = resolveCompetition();
|
|
3825
3976
|
const baseUrl = competition === DEFAULT_COMPETITION ? void 0 : competitionBase(competition);
|
|
3826
3977
|
return new EspnAdapter({ baseUrl });
|
|
3827
3978
|
}
|
|
3979
|
+
default:
|
|
3980
|
+
throw new Error(
|
|
3981
|
+
`Unknown data source "${source}" (available: ${KNOWN_SOURCES.join(", ")})`
|
|
3982
|
+
);
|
|
3828
3983
|
}
|
|
3829
3984
|
}
|
|
3830
3985
|
function mergeLive(base, live) {
|
|
@@ -3862,8 +4017,16 @@ async function getStandings(adapter, group) {
|
|
|
3862
4017
|
const tables = letters.map((g) => ({ group: g, rows: rosterAtZero(fixturesByGroup(g)) })).filter((t2) => t2.rows.length > 0);
|
|
3863
4018
|
return { tables, degraded: true };
|
|
3864
4019
|
}
|
|
3865
|
-
var
|
|
3866
|
-
|
|
4020
|
+
var knockoutWindowMemo;
|
|
4021
|
+
function knockoutWindow() {
|
|
4022
|
+
if (knockoutWindowMemo !== void 0) return knockoutWindowMemo;
|
|
4023
|
+
const days = allFixtures().filter((m) => m.stage !== "GROUP" && m.stage !== "FRIENDLY").map((m) => m.kickoff.slice(0, 10).replace(/-/g, "")).filter((d) => d.length === 8);
|
|
4024
|
+
knockoutWindowMemo = days.length ? {
|
|
4025
|
+
start: days.reduce((a, b) => a < b ? a : b),
|
|
4026
|
+
end: days.reduce((a, b) => a > b ? a : b)
|
|
4027
|
+
} : null;
|
|
4028
|
+
return knockoutWindowMemo;
|
|
4029
|
+
}
|
|
3867
4030
|
async function getBracket(adapter, opts = {}) {
|
|
3868
4031
|
const topology = loadBracketTopology();
|
|
3869
4032
|
const base = allFixtures().filter((m) => m.stage !== "GROUP" && m.stage !== "FRIENDLY");
|
|
@@ -3871,7 +4034,8 @@ async function getBracket(adapter, opts = {}) {
|
|
|
3871
4034
|
let liveDegraded = true;
|
|
3872
4035
|
let source;
|
|
3873
4036
|
try {
|
|
3874
|
-
const
|
|
4037
|
+
const win = knockoutWindow();
|
|
4038
|
+
const live = adapter.fetchWindow && win ? await adapter.fetchWindow(win.start, win.end) : [];
|
|
3875
4039
|
matches = mergeLive(base, live);
|
|
3876
4040
|
liveDegraded = false;
|
|
3877
4041
|
source = adapter.name;
|
|
@@ -3904,8 +4068,9 @@ async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Dat
|
|
|
3904
4068
|
let fixtures = allFixtures();
|
|
3905
4069
|
let overlayFailed = false;
|
|
3906
4070
|
try {
|
|
3907
|
-
|
|
3908
|
-
|
|
4071
|
+
const win = knockoutWindow();
|
|
4072
|
+
if (adapter.fetchWindow && win) {
|
|
4073
|
+
fixtures = mergeLive(fixtures, await adapter.fetchWindow(win.start, win.end));
|
|
3909
4074
|
}
|
|
3910
4075
|
} catch {
|
|
3911
4076
|
overlayFailed = true;
|
|
@@ -3928,7 +4093,8 @@ async function getNextFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Da
|
|
|
3928
4093
|
let degraded = true;
|
|
3929
4094
|
let liveById;
|
|
3930
4095
|
try {
|
|
3931
|
-
const
|
|
4096
|
+
const win = knockoutWindow();
|
|
4097
|
+
const live = adapter.fetchWindow && win ? await adapter.fetchWindow(win.start, win.end) : [];
|
|
3932
4098
|
matches = mergeLive(base, live);
|
|
3933
4099
|
degraded = false;
|
|
3934
4100
|
liveById = new Set(live.map((m) => m.id));
|
|
@@ -3939,10 +4105,11 @@ async function getNextFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Da
|
|
|
3939
4105
|
return { fixture, degraded, source };
|
|
3940
4106
|
}
|
|
3941
4107
|
async function getKnockoutFixtures(adapter, now = /* @__PURE__ */ new Date()) {
|
|
3942
|
-
|
|
4108
|
+
const win = knockoutWindow();
|
|
4109
|
+
if (!adapter.fetchWindow || !win) return { fixtures: [], degraded: true };
|
|
3943
4110
|
let live;
|
|
3944
4111
|
try {
|
|
3945
|
-
live = await adapter.fetchWindow(
|
|
4112
|
+
live = await adapter.fetchWindow(win.start, win.end);
|
|
3946
4113
|
} catch {
|
|
3947
4114
|
return { fixtures: [], degraded: true };
|
|
3948
4115
|
}
|
|
@@ -4190,7 +4357,7 @@ var mapping_2026_default = {
|
|
|
4190
4357
|
var DEFAULT_BASE2 = "https://gamma-api.polymarket.com";
|
|
4191
4358
|
var ALLOWED_HOSTS = /* @__PURE__ */ new Set(["gamma-api.polymarket.com"]);
|
|
4192
4359
|
var USER_AGENT2 = "claudinho/0.0 (+https://github.com/arturogarrido/claudinho)";
|
|
4193
|
-
var
|
|
4360
|
+
var DEFAULT_TIMEOUT_MS2 = 8e3;
|
|
4194
4361
|
var WC_SERIES_SLUG = "soccer-fifwc";
|
|
4195
4362
|
var WC_SPORT = "fifwc";
|
|
4196
4363
|
var KICKOFF_TOLERANCE_MS = 6 * 60 * 6e4;
|
|
@@ -4228,7 +4395,7 @@ var PolymarketProvider = class {
|
|
|
4228
4395
|
const entry = (this.opts.mapping ?? BUNDLED_MAPPING)[match.id];
|
|
4229
4396
|
const slugs = entry?.eventSlug ? [entry.eventSlug] : deriveEventSlugs(match);
|
|
4230
4397
|
if (slugs.length === 0) return { checked: true };
|
|
4231
|
-
const configured = options?.timeoutMs ?? this.opts.timeoutMs ??
|
|
4398
|
+
const configured = options?.timeoutMs ?? this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
4232
4399
|
try {
|
|
4233
4400
|
for (const slug of slugs) {
|
|
4234
4401
|
const remaining = deadline - Date.now();
|
|
@@ -4248,13 +4415,20 @@ var PolymarketProvider = class {
|
|
|
4248
4415
|
const url = `${base}/events?slug=${encodeURIComponent(slug)}`;
|
|
4249
4416
|
const doFetch = this.opts.fetchImpl ?? fetch;
|
|
4250
4417
|
const res = await doFetch(url, {
|
|
4251
|
-
signal: AbortSignal.timeout(timeoutMs ?? this.opts.timeoutMs ??
|
|
4418
|
+
signal: AbortSignal.timeout(timeoutMs ?? this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2),
|
|
4419
|
+
// Gamma never legitimately redirects; following one would sidestep the
|
|
4420
|
+
// host allow-list (it only validates the base URL), so reject redirects.
|
|
4421
|
+
redirect: "error",
|
|
4252
4422
|
headers: { Accept: "application/json", "User-Agent": USER_AGENT2 }
|
|
4253
4423
|
});
|
|
4254
4424
|
if (res.status === 404) return void 0;
|
|
4255
4425
|
if (!res.ok) {
|
|
4256
4426
|
throw new Error(`Polymarket request failed: ${res.status} ${res.statusText}`);
|
|
4257
4427
|
}
|
|
4428
|
+
const length = Number(res.headers?.get?.("content-length"));
|
|
4429
|
+
if (Number.isFinite(length) && length > MAX_RESPONSE_BYTES) {
|
|
4430
|
+
throw new Error(`Polymarket response too large: ${length} bytes`);
|
|
4431
|
+
}
|
|
4258
4432
|
const data = await res.json();
|
|
4259
4433
|
const event = Array.isArray(data) ? data[0] : data;
|
|
4260
4434
|
return event && typeof event === "object" ? event : void 0;
|
|
@@ -4732,6 +4906,82 @@ function resolveConfig(opts) {
|
|
|
4732
4906
|
};
|
|
4733
4907
|
}
|
|
4734
4908
|
|
|
4909
|
+
// src/cursorPayload.ts
|
|
4910
|
+
import { readFileSync } from "fs";
|
|
4911
|
+
function parseCursorPayload(raw) {
|
|
4912
|
+
try {
|
|
4913
|
+
const trimmed = raw.trim();
|
|
4914
|
+
if (!trimmed) return void 0;
|
|
4915
|
+
return JSON.parse(trimmed);
|
|
4916
|
+
} catch {
|
|
4917
|
+
return void 0;
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
function readCursorPayload() {
|
|
4921
|
+
try {
|
|
4922
|
+
if (process.stdin.isTTY) return void 0;
|
|
4923
|
+
return parseCursorPayload(readFileSync(0, "utf8"));
|
|
4924
|
+
} catch {
|
|
4925
|
+
return void 0;
|
|
4926
|
+
}
|
|
4927
|
+
}
|
|
4928
|
+
function readCursorPayloadBounded(timeoutMs = 100, stdin = process.stdin) {
|
|
4929
|
+
if (stdin.isTTY) return Promise.resolve(void 0);
|
|
4930
|
+
return new Promise((resolve) => {
|
|
4931
|
+
const chunks = [];
|
|
4932
|
+
const done = () => {
|
|
4933
|
+
clearTimeout(timer);
|
|
4934
|
+
stdin.off("data", onData);
|
|
4935
|
+
stdin.off("end", done);
|
|
4936
|
+
stdin.off("error", done);
|
|
4937
|
+
stdin.pause();
|
|
4938
|
+
resolve(parseCursorPayload(Buffer.concat(chunks).toString("utf8")));
|
|
4939
|
+
};
|
|
4940
|
+
const onData = (c) => {
|
|
4941
|
+
chunks.push(c);
|
|
4942
|
+
};
|
|
4943
|
+
const timer = setTimeout(done, timeoutMs);
|
|
4944
|
+
timer.unref?.();
|
|
4945
|
+
stdin.on("data", onData);
|
|
4946
|
+
stdin.once("end", done);
|
|
4947
|
+
stdin.once("error", done);
|
|
4948
|
+
stdin.resume();
|
|
4949
|
+
});
|
|
4950
|
+
}
|
|
4951
|
+
function looksLikeCursorPayload(payload) {
|
|
4952
|
+
return !!(payload.model?.display_name || payload.context_window != null || payload.render_width_chars != null || payload.worktree?.name || payload.vim?.mode);
|
|
4953
|
+
}
|
|
4954
|
+
function cursorMetaEnabled(payload) {
|
|
4955
|
+
const v = (process.env.CLAUDINHO_CURSOR_META ?? "").toLowerCase();
|
|
4956
|
+
if (v === "0" || v === "false" || v === "no") return false;
|
|
4957
|
+
if (v === "1" || v === "true" || v === "yes") return !!payload;
|
|
4958
|
+
if (v === "auto") return !!payload && looksLikeCursorPayload(payload);
|
|
4959
|
+
return false;
|
|
4960
|
+
}
|
|
4961
|
+
function renderCursorMetaLine(payload) {
|
|
4962
|
+
const parts = [];
|
|
4963
|
+
const model = payload.model?.display_name;
|
|
4964
|
+
if (model) {
|
|
4965
|
+
let label = model;
|
|
4966
|
+
if (payload.model?.param_summary) label += ` ${payload.model.param_summary}`;
|
|
4967
|
+
parts.push(label);
|
|
4968
|
+
}
|
|
4969
|
+
const pct2 = payload.context_window?.used_percentage;
|
|
4970
|
+
if (typeof pct2 === "number" && Number.isFinite(pct2)) {
|
|
4971
|
+
parts.push(`ctx ${Math.floor(pct2)}%`);
|
|
4972
|
+
}
|
|
4973
|
+
if (payload.worktree?.name) parts.push(`wt ${payload.worktree.name}`);
|
|
4974
|
+
if (payload.vim?.mode) parts.push(payload.vim.mode);
|
|
4975
|
+
if (parts.length === 0) return void 0;
|
|
4976
|
+
return `\x1B[90m${parts.join(" ")}\x1B[0m`;
|
|
4977
|
+
}
|
|
4978
|
+
function renderPromptOutput(scoreLine, payload) {
|
|
4979
|
+
const meta = payload && cursorMetaEnabled(payload) ? renderCursorMetaLine(payload) : void 0;
|
|
4980
|
+
if (meta) return `${scoreLine}
|
|
4981
|
+
${meta}`;
|
|
4982
|
+
return scoreLine;
|
|
4983
|
+
}
|
|
4984
|
+
|
|
4735
4985
|
// src/i18n.ts
|
|
4736
4986
|
var EN2 = {
|
|
4737
4987
|
"today.title": "Today's matches",
|
|
@@ -4753,7 +5003,6 @@ var EN2 = {
|
|
|
4753
5003
|
"table.degraded": "Live standings unavailable \u2014 showing the group roster.",
|
|
4754
5004
|
"table.empty": "No standings available.",
|
|
4755
5005
|
"match.none": "No match found with id {id}.",
|
|
4756
|
-
"status.scheduled": "scheduled",
|
|
4757
5006
|
"status.live": "LIVE",
|
|
4758
5007
|
"status.ht": "HT",
|
|
4759
5008
|
"status.ft": "FT",
|
|
@@ -4767,6 +5016,7 @@ var EN2 = {
|
|
|
4767
5016
|
"col.gd": "GD",
|
|
4768
5017
|
"col.pts": "Pts",
|
|
4769
5018
|
"err.date": "Invalid date {date}. Use YYYY-MM-DD.",
|
|
5019
|
+
"err.source": "Unknown data source {source}. Available: {sources}.",
|
|
4770
5020
|
"warn.tz": "Unknown timezone {tz}; using system timezone.",
|
|
4771
5021
|
"warn.lang": "Unsupported language {lang}; using English. (supported: en, es, pt, fr)",
|
|
4772
5022
|
disclaimer: "Not affiliated with FIFA or Anthropic."
|
|
@@ -4791,7 +5041,6 @@ var ES2 = {
|
|
|
4791
5041
|
"table.degraded": "Tabla en vivo no disponible \u2014 mostrando la lista del grupo.",
|
|
4792
5042
|
"table.empty": "No hay clasificaci\xF3n disponible.",
|
|
4793
5043
|
"match.none": "No se encontr\xF3 partido con id {id}.",
|
|
4794
|
-
"status.scheduled": "programado",
|
|
4795
5044
|
"status.live": "EN VIVO",
|
|
4796
5045
|
"status.ht": "DESC",
|
|
4797
5046
|
"status.ft": "FIN",
|
|
@@ -4805,6 +5054,7 @@ var ES2 = {
|
|
|
4805
5054
|
"col.gd": "DG",
|
|
4806
5055
|
"col.pts": "Pts",
|
|
4807
5056
|
"err.date": "Fecha inv\xE1lida {date}. Usa AAAA-MM-DD.",
|
|
5057
|
+
"err.source": "Fuente de datos desconocida {source}. Disponibles: {sources}.",
|
|
4808
5058
|
"warn.tz": "Zona horaria desconocida {tz}; usando la del sistema.",
|
|
4809
5059
|
"warn.lang": "Idioma no soportado {lang}; usando ingl\xE9s. (disponibles: en, es, pt, fr)",
|
|
4810
5060
|
disclaimer: "No afiliado a FIFA ni Anthropic."
|
|
@@ -4829,7 +5079,6 @@ var PT2 = {
|
|
|
4829
5079
|
"table.degraded": "Classifica\xE7\xE3o ao vivo indispon\xEDvel \u2014 mostrando os times do grupo.",
|
|
4830
5080
|
"table.empty": "Classifica\xE7\xE3o indispon\xEDvel.",
|
|
4831
5081
|
"match.none": "Nenhum jogo encontrado com id {id}.",
|
|
4832
|
-
"status.scheduled": "agendado",
|
|
4833
5082
|
"status.live": "AO VIVO",
|
|
4834
5083
|
"status.ht": "INT",
|
|
4835
5084
|
"status.ft": "FIM",
|
|
@@ -4843,6 +5092,7 @@ var PT2 = {
|
|
|
4843
5092
|
"col.gd": "SG",
|
|
4844
5093
|
"col.pts": "Pts",
|
|
4845
5094
|
"err.date": "Data inv\xE1lida {date}. Use AAAA-MM-DD.",
|
|
5095
|
+
"err.source": "Fonte de dados desconhecida {source}. Dispon\xEDveis: {sources}.",
|
|
4846
5096
|
"warn.tz": "Fuso hor\xE1rio desconhecido {tz}; usando o do sistema.",
|
|
4847
5097
|
"warn.lang": "Idioma n\xE3o suportado {lang}; usando ingl\xEAs. (dispon\xEDveis: en, es, pt, fr)",
|
|
4848
5098
|
disclaimer: "N\xE3o afiliado \xE0 FIFA nem \xE0 Anthropic."
|
|
@@ -4867,7 +5117,6 @@ var FR2 = {
|
|
|
4867
5117
|
"table.degraded": "Classement en direct indisponible \u2014 affichage de la composition du groupe.",
|
|
4868
5118
|
"table.empty": "Aucun classement disponible.",
|
|
4869
5119
|
"match.none": "Aucun match trouv\xE9 avec id {id}.",
|
|
4870
|
-
"status.scheduled": "pr\xE9vu",
|
|
4871
5120
|
"status.live": "DIRECT",
|
|
4872
5121
|
"status.ht": "MT",
|
|
4873
5122
|
"status.ft": "FIN",
|
|
@@ -4881,6 +5130,7 @@ var FR2 = {
|
|
|
4881
5130
|
"col.gd": "Diff",
|
|
4882
5131
|
"col.pts": "Pts",
|
|
4883
5132
|
"err.date": "Date invalide {date}. Utilisez AAAA-MM-JJ.",
|
|
5133
|
+
"err.source": "Source de donn\xE9es inconnue {source}. Disponibles : {sources}.",
|
|
4884
5134
|
"warn.tz": "Fuseau horaire inconnu {tz} ; utilisation du fuseau syst\xE8me.",
|
|
4885
5135
|
"warn.lang": "Langue non prise en charge {lang} ; utilisation de l\u2019anglais. (disponibles : en, es, pt, fr)",
|
|
4886
5136
|
disclaimer: "Non affili\xE9 \xE0 la FIFA ni \xE0 Anthropic."
|
|
@@ -4888,9 +5138,10 @@ var FR2 = {
|
|
|
4888
5138
|
var CATALOGS2 = { en: EN2, es: ES2, pt: PT2, fr: FR2 };
|
|
4889
5139
|
function makeT(lang) {
|
|
4890
5140
|
const dict = CATALOGS2[lang] ?? EN2;
|
|
5141
|
+
const en = EN2;
|
|
4891
5142
|
return (key, vars) => {
|
|
4892
|
-
let s = dict[key] ??
|
|
4893
|
-
if (vars) for (const [k, v] of Object.entries(vars)) s = s.
|
|
5143
|
+
let s = dict[key] ?? en[key] ?? key;
|
|
5144
|
+
if (vars) for (const [k, v] of Object.entries(vars)) s = s.replaceAll(`{${k}}`, v);
|
|
4894
5145
|
return s;
|
|
4895
5146
|
};
|
|
4896
5147
|
}
|
|
@@ -4949,7 +5200,7 @@ function matchLine(m, cfg, t2, c, flags = true) {
|
|
|
4949
5200
|
const home = flags ? `${m.home.flag} ${m.home.name}` : m.home.name;
|
|
4950
5201
|
const away = flags ? `${m.away.name} ${m.away.flag}` : m.away.name;
|
|
4951
5202
|
const mid2 = isLive(m.status) || m.status === "FT" ? c.bold(scoreline(m)) : c.dim("vs");
|
|
4952
|
-
const left = `${home
|
|
5203
|
+
const left = `${padVisible(home, 22)} ${mid2.padStart(3)} ${away}`;
|
|
4953
5204
|
let right = "";
|
|
4954
5205
|
if (m.status === "SCHEDULED") {
|
|
4955
5206
|
right = c.dim(
|
|
@@ -4974,21 +5225,38 @@ function dataSource(source, lang, c) {
|
|
|
4974
5225
|
}
|
|
4975
5226
|
|
|
4976
5227
|
// src/marketCache.ts
|
|
4977
|
-
import {
|
|
5228
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
5229
|
+
import { join as join2 } from "path";
|
|
5230
|
+
|
|
5231
|
+
// src/paths.ts
|
|
5232
|
+
import { closeSync, mkdirSync, openSync, renameSync, writeSync } from "fs";
|
|
4978
5233
|
import { homedir } from "os";
|
|
4979
|
-
import { join } from "path";
|
|
4980
|
-
var POSITIVE_TTL_MS = 10 * 6e4;
|
|
4981
|
-
var NEGATIVE_TTL_MS = 3 * 6e4;
|
|
5234
|
+
import { dirname, join } from "path";
|
|
4982
5235
|
function cacheDir() {
|
|
4983
5236
|
const base = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
|
|
4984
5237
|
return join(base, "claudinho");
|
|
4985
5238
|
}
|
|
5239
|
+
function writeFileAtomic(path, data) {
|
|
5240
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
5241
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
5242
|
+
const fd = openSync(tmp, "w");
|
|
5243
|
+
try {
|
|
5244
|
+
writeSync(fd, data);
|
|
5245
|
+
} finally {
|
|
5246
|
+
closeSync(fd);
|
|
5247
|
+
}
|
|
5248
|
+
renameSync(tmp, path);
|
|
5249
|
+
}
|
|
5250
|
+
|
|
5251
|
+
// src/marketCache.ts
|
|
5252
|
+
var POSITIVE_TTL_MS = 10 * 6e4;
|
|
5253
|
+
var NEGATIVE_TTL_MS = 3 * 6e4;
|
|
4986
5254
|
function cachePath() {
|
|
4987
|
-
return
|
|
5255
|
+
return join2(cacheDir(), "market-signals.json");
|
|
4988
5256
|
}
|
|
4989
5257
|
function readFile() {
|
|
4990
5258
|
try {
|
|
4991
|
-
return JSON.parse(
|
|
5259
|
+
return JSON.parse(readFileSync2(cachePath(), "utf8"));
|
|
4992
5260
|
} catch {
|
|
4993
5261
|
return void 0;
|
|
4994
5262
|
}
|
|
@@ -5019,23 +5287,18 @@ function writeMarketCache(source, competition, attempted, fetched, now = Date.no
|
|
|
5019
5287
|
for (const id of attempted) {
|
|
5020
5288
|
base.entries[id] = { fetchedAt, signal: fetched.get(id) ?? null };
|
|
5021
5289
|
}
|
|
5022
|
-
|
|
5023
|
-
const tmp = join(cacheDir(), `market-signals.${process.pid}.tmp`);
|
|
5024
|
-
writeFileSync(tmp, JSON.stringify(base));
|
|
5025
|
-
renameSync(tmp, cachePath());
|
|
5290
|
+
writeFileAtomic(cachePath(), JSON.stringify(base));
|
|
5026
5291
|
} catch {
|
|
5027
5292
|
}
|
|
5028
5293
|
}
|
|
5029
5294
|
|
|
5030
5295
|
// src/starNudge.ts
|
|
5031
|
-
import {
|
|
5032
|
-
import {
|
|
5033
|
-
import { dirname, join as join2 } from "path";
|
|
5296
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
5297
|
+
import { join as join3 } from "path";
|
|
5034
5298
|
var REPO_URL = "https://github.com/arturogarrido/claudinho";
|
|
5035
5299
|
var NUDGE_EVERY = 5;
|
|
5036
5300
|
function counterPath() {
|
|
5037
|
-
|
|
5038
|
-
return join2(base, "claudinho", "runs.json");
|
|
5301
|
+
return join3(cacheDir(), "runs.json");
|
|
5039
5302
|
}
|
|
5040
5303
|
function shouldNudge(runCount, every = NUDGE_EVERY) {
|
|
5041
5304
|
return runCount > 0 && runCount % every === 0;
|
|
@@ -5044,14 +5307,13 @@ function bumpRunCount(path = counterPath()) {
|
|
|
5044
5307
|
try {
|
|
5045
5308
|
let count = 0;
|
|
5046
5309
|
try {
|
|
5047
|
-
const raw = JSON.parse(
|
|
5310
|
+
const raw = JSON.parse(readFileSync3(path, "utf8"));
|
|
5048
5311
|
if (typeof raw.count === "number" && Number.isFinite(raw.count)) count = raw.count;
|
|
5049
5312
|
} catch {
|
|
5050
5313
|
count = 0;
|
|
5051
5314
|
}
|
|
5052
5315
|
count += 1;
|
|
5053
|
-
|
|
5054
|
-
writeFileSync2(path, JSON.stringify({ count }), "utf8");
|
|
5316
|
+
writeFileAtomic(path, JSON.stringify({ count }));
|
|
5055
5317
|
return count;
|
|
5056
5318
|
} catch {
|
|
5057
5319
|
return void 0;
|
|
@@ -5086,53 +5348,56 @@ function copyToClipboard(text, platform = process.platform) {
|
|
|
5086
5348
|
|
|
5087
5349
|
// src/cache.ts
|
|
5088
5350
|
import {
|
|
5089
|
-
closeSync,
|
|
5090
|
-
mkdirSync as
|
|
5091
|
-
openSync,
|
|
5092
|
-
readFileSync as
|
|
5093
|
-
renameSync as renameSync2,
|
|
5351
|
+
closeSync as closeSync2,
|
|
5352
|
+
mkdirSync as mkdirSync2,
|
|
5353
|
+
openSync as openSync2,
|
|
5354
|
+
readFileSync as readFileSync4,
|
|
5094
5355
|
rmSync,
|
|
5095
5356
|
statSync,
|
|
5096
|
-
writeSync
|
|
5357
|
+
writeSync as writeSync2
|
|
5097
5358
|
} from "fs";
|
|
5098
|
-
import {
|
|
5099
|
-
|
|
5359
|
+
import { join as join4 } from "path";
|
|
5360
|
+
var CACHE_VERSION = 2;
|
|
5100
5361
|
var LOCK_STALE_MS = 6e4;
|
|
5101
|
-
function
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
}
|
|
5105
|
-
|
|
5106
|
-
return
|
|
5362
|
+
function cachePath2(source = "espn", competition = DEFAULT_COMPETITION) {
|
|
5363
|
+
if (source === "espn" && competition === DEFAULT_COMPETITION) {
|
|
5364
|
+
return join4(cacheDir(), "state.json");
|
|
5365
|
+
}
|
|
5366
|
+
const slug = `${source}.${competition}`.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
5367
|
+
return join4(cacheDir(), `state.${slug}.json`);
|
|
5107
5368
|
}
|
|
5108
5369
|
function lockPath() {
|
|
5109
|
-
return
|
|
5370
|
+
return join4(cacheDir(), "refresh.lock");
|
|
5110
5371
|
}
|
|
5111
|
-
function readState() {
|
|
5372
|
+
function readState(source = "espn", competition = DEFAULT_COMPETITION) {
|
|
5112
5373
|
try {
|
|
5113
|
-
|
|
5374
|
+
const s = JSON.parse(
|
|
5375
|
+
readFileSync4(cachePath2(source, competition), "utf8")
|
|
5376
|
+
);
|
|
5377
|
+
return s.version === CACHE_VERSION ? s : void 0;
|
|
5114
5378
|
} catch {
|
|
5115
5379
|
return void 0;
|
|
5116
5380
|
}
|
|
5117
5381
|
}
|
|
5118
5382
|
function readCurrentState(source, competition) {
|
|
5119
|
-
const s = readState();
|
|
5383
|
+
const s = readState(source, competition);
|
|
5120
5384
|
return s && s.source === source && s.competition === competition ? s : void 0;
|
|
5121
5385
|
}
|
|
5122
5386
|
function writeState(state) {
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
const
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5387
|
+
writeFileAtomic(
|
|
5388
|
+
cachePath2(state.source, state.competition),
|
|
5389
|
+
JSON.stringify({ ...state, version: CACHE_VERSION })
|
|
5390
|
+
);
|
|
5391
|
+
}
|
|
5392
|
+
function backoffActive(state, now = Date.now()) {
|
|
5393
|
+
if (!state?.backoffUntil) return false;
|
|
5394
|
+
const t2 = Date.parse(state.backoffUntil);
|
|
5395
|
+
return Number.isFinite(t2) && now < t2;
|
|
5396
|
+
}
|
|
5397
|
+
function fixturesAttemptAgeMs(state, now = Date.now()) {
|
|
5398
|
+
if (!state?.fixturesAttemptedAt) return Infinity;
|
|
5399
|
+
const t2 = Date.parse(state.fixturesAttemptedAt);
|
|
5400
|
+
return Number.isFinite(t2) ? now - t2 : Infinity;
|
|
5136
5401
|
}
|
|
5137
5402
|
function ageMs(state, now = Date.now()) {
|
|
5138
5403
|
if (!state) return Infinity;
|
|
@@ -5147,7 +5412,7 @@ function fixturesAgeMs(state, now = Date.now()) {
|
|
|
5147
5412
|
function lockAgeMs(now = Date.now()) {
|
|
5148
5413
|
const lp = lockPath();
|
|
5149
5414
|
try {
|
|
5150
|
-
const contents =
|
|
5415
|
+
const contents = readFileSync4(lp, "utf8");
|
|
5151
5416
|
const written = Number.parseInt(contents.split(/\s+/)[1] ?? "", 10);
|
|
5152
5417
|
if (Number.isFinite(written)) return now - written;
|
|
5153
5418
|
} catch {
|
|
@@ -5163,14 +5428,14 @@ function isLockFresh(now = Date.now()) {
|
|
|
5163
5428
|
return lockAgeMs(now) < LOCK_STALE_MS;
|
|
5164
5429
|
}
|
|
5165
5430
|
function acquireLock(now = Date.now()) {
|
|
5166
|
-
|
|
5431
|
+
mkdirSync2(cacheDir(), { recursive: true });
|
|
5167
5432
|
const lp = lockPath();
|
|
5168
5433
|
try {
|
|
5169
|
-
const fd =
|
|
5434
|
+
const fd = openSync2(lp, "wx");
|
|
5170
5435
|
try {
|
|
5171
|
-
|
|
5436
|
+
writeSync2(fd, `${process.pid} ${now}`);
|
|
5172
5437
|
} finally {
|
|
5173
|
-
|
|
5438
|
+
closeSync2(fd);
|
|
5174
5439
|
}
|
|
5175
5440
|
return true;
|
|
5176
5441
|
} catch {
|
|
@@ -5181,11 +5446,11 @@ function acquireLock(now = Date.now()) {
|
|
|
5181
5446
|
return false;
|
|
5182
5447
|
}
|
|
5183
5448
|
try {
|
|
5184
|
-
const fd =
|
|
5449
|
+
const fd = openSync2(lp, "wx");
|
|
5185
5450
|
try {
|
|
5186
|
-
|
|
5451
|
+
writeSync2(fd, `${process.pid} ${now}`);
|
|
5187
5452
|
} finally {
|
|
5188
|
-
|
|
5453
|
+
closeSync2(fd);
|
|
5189
5454
|
}
|
|
5190
5455
|
return true;
|
|
5191
5456
|
} catch {
|
|
@@ -5218,6 +5483,10 @@ function inLiveWindow(now = Date.now(), fixtures = allFixtures()) {
|
|
|
5218
5483
|
function isResolvedFixture(m) {
|
|
5219
5484
|
return isResolvedNation(m.home) && isResolvedNation(m.away);
|
|
5220
5485
|
}
|
|
5486
|
+
function isMatchShaped(m) {
|
|
5487
|
+
const x = m;
|
|
5488
|
+
return !!x && typeof x === "object" && typeof x.id === "string" && typeof x.kickoff === "string" && !!x.home?.code && !!x.away?.code;
|
|
5489
|
+
}
|
|
5221
5490
|
function nextOverall(now, fixtures = allFixtures()) {
|
|
5222
5491
|
return [...fixtures].sort(byKickoff).find((m) => Date.parse(m.kickoff) >= now && isResolvedFixture(m));
|
|
5223
5492
|
}
|
|
@@ -5238,7 +5507,7 @@ function liveMatchesFromCache(state, nowMs = Date.now()) {
|
|
|
5238
5507
|
const liveArr = fresh && Array.isArray(state?.live) ? state.live : [];
|
|
5239
5508
|
return liveArr.filter(
|
|
5240
5509
|
(m) => !!m && typeof m === "object" && isLive(m.status) && !!m.home?.code && !!m.away?.code
|
|
5241
|
-
);
|
|
5510
|
+
).map(sanitizeMatchStrings);
|
|
5242
5511
|
}
|
|
5243
5512
|
function renderPrompt(state, opts = {}) {
|
|
5244
5513
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
@@ -5247,7 +5516,7 @@ function renderPrompt(state, opts = {}) {
|
|
|
5247
5516
|
const flags = opts.flags ?? true;
|
|
5248
5517
|
const team = opts.team?.toUpperCase();
|
|
5249
5518
|
const live = liveMatchesFromCache(state, nowMs);
|
|
5250
|
-
const cachedFixtures = Array.isArray(state?.fixtures) ? state.fixtures : [];
|
|
5519
|
+
const cachedFixtures = Array.isArray(state?.fixtures) ? state.fixtures.filter(isMatchShaped).map(sanitizeMatchStrings) : [];
|
|
5251
5520
|
const schedule = cachedFixtures.length ? mergeLive(allFixtures(), cachedFixtures) : void 0;
|
|
5252
5521
|
if (team) {
|
|
5253
5522
|
const mine = live.find((m) => m.home?.code === team || m.away?.code === team);
|
|
@@ -5280,10 +5549,16 @@ function renderPrompt(state, opts = {}) {
|
|
|
5280
5549
|
}
|
|
5281
5550
|
|
|
5282
5551
|
// src/hook.ts
|
|
5552
|
+
function rosterPinned(t2) {
|
|
5553
|
+
const { team } = lookupTeam(t2.code);
|
|
5554
|
+
return team ? { ...t2, name: team.name, flag: team.flag } : t2;
|
|
5555
|
+
}
|
|
5283
5556
|
function line(m, flags) {
|
|
5284
5557
|
const minute = m.status === "HT" ? "half-time" : m.minute ? `${m.minute}'` : "live";
|
|
5285
|
-
const
|
|
5286
|
-
const
|
|
5558
|
+
const h = rosterPinned(m.home);
|
|
5559
|
+
const a = rosterPinned(m.away);
|
|
5560
|
+
const home = flags ? `${h.flag} ${h.name}` : h.name;
|
|
5561
|
+
const away = flags ? `${a.name} ${a.flag}` : a.name;
|
|
5287
5562
|
return `${home} ${scoreline(m)} ${away} (${minute})`;
|
|
5288
5563
|
}
|
|
5289
5564
|
function renderHook(state, opts = {}) {
|
|
@@ -5309,9 +5584,12 @@ import { spawn } from "child_process";
|
|
|
5309
5584
|
var MIN_REFRESH_MS = 12e3;
|
|
5310
5585
|
var FIXTURES_TTL_MS = 15 * 6e4;
|
|
5311
5586
|
var FIXTURES_EMPTY_TTL_MS = 6e4;
|
|
5587
|
+
var BACKOFF_MS = 5 * 6e4;
|
|
5588
|
+
var BACKOFF_JITTER_MS = 6e4;
|
|
5312
5589
|
function fixturesStale(state, now) {
|
|
5313
5590
|
const ttl = (state?.fixtures?.length ?? 0) > 0 ? FIXTURES_TTL_MS : FIXTURES_EMPTY_TTL_MS;
|
|
5314
|
-
|
|
5591
|
+
if (fixturesAgeMs(state, now) <= ttl) return false;
|
|
5592
|
+
return fixturesAttemptAgeMs(state, now) > FIXTURES_EMPTY_TTL_MS;
|
|
5315
5593
|
}
|
|
5316
5594
|
function nextStaticUpcoming(nowMs) {
|
|
5317
5595
|
return [...allFixtures()].sort(byKickoff).find((m) => Date.parse(m.kickoff) >= nowMs);
|
|
@@ -5325,24 +5603,57 @@ function liveWindowActive(nowMs) {
|
|
|
5325
5603
|
if (resolveCompetition() !== DEFAULT_COMPETITION) return true;
|
|
5326
5604
|
return inLiveWindow(nowMs);
|
|
5327
5605
|
}
|
|
5328
|
-
function liveAdapter() {
|
|
5606
|
+
function liveAdapter(source) {
|
|
5329
5607
|
const competition = resolveCompetition();
|
|
5330
|
-
if (competition === DEFAULT_COMPETITION) {
|
|
5608
|
+
if (source === "espn" && competition === DEFAULT_COMPETITION) {
|
|
5331
5609
|
return new EspnAdapter({ enrichGroups: false });
|
|
5332
5610
|
}
|
|
5333
|
-
return makeAdapter(
|
|
5611
|
+
return makeAdapter(source);
|
|
5334
5612
|
}
|
|
5335
5613
|
async function runRefresh(opts = {}) {
|
|
5336
5614
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
5337
5615
|
const nowMs = now.getTime();
|
|
5338
5616
|
const source = opts.source ?? "espn";
|
|
5339
5617
|
const competition = resolveCompetition();
|
|
5340
|
-
|
|
5618
|
+
if (!KNOWN_SOURCES.includes(source)) {
|
|
5619
|
+
if (!readState(source, competition) && acquireLock()) {
|
|
5620
|
+
try {
|
|
5621
|
+
writeState({
|
|
5622
|
+
updatedAt: now.toISOString(),
|
|
5623
|
+
live: [],
|
|
5624
|
+
degraded: true,
|
|
5625
|
+
// no live provider served this scope โ never claim otherwise
|
|
5626
|
+
source,
|
|
5627
|
+
competition
|
|
5628
|
+
});
|
|
5629
|
+
} finally {
|
|
5630
|
+
releaseLock();
|
|
5631
|
+
}
|
|
5632
|
+
}
|
|
5633
|
+
return;
|
|
5634
|
+
}
|
|
5635
|
+
const cached = readState(source, competition);
|
|
5341
5636
|
const sameScope = !!cached && cached.source === source && cached.competition === competition;
|
|
5342
5637
|
const base = sameScope ? cached : void 0;
|
|
5343
|
-
const
|
|
5344
|
-
const
|
|
5345
|
-
|
|
5638
|
+
const inBackoff = backoffActive(base, nowMs);
|
|
5639
|
+
const needLive = !inBackoff && liveWindowActive(nowMs) && (!base || ageMs(base, nowMs) >= MIN_REFRESH_MS);
|
|
5640
|
+
const needFixtures = !inBackoff && inKnockoutPhase(nowMs) && fixturesStale(base, nowMs);
|
|
5641
|
+
if (!needLive && !needFixtures) {
|
|
5642
|
+
if (!base && acquireLock()) {
|
|
5643
|
+
try {
|
|
5644
|
+
writeState({
|
|
5645
|
+
updatedAt: now.toISOString(),
|
|
5646
|
+
live: [],
|
|
5647
|
+
degraded: false,
|
|
5648
|
+
source,
|
|
5649
|
+
competition
|
|
5650
|
+
});
|
|
5651
|
+
} finally {
|
|
5652
|
+
releaseLock();
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
return;
|
|
5656
|
+
}
|
|
5346
5657
|
if (!acquireLock()) return;
|
|
5347
5658
|
try {
|
|
5348
5659
|
let live = base?.live ?? [];
|
|
@@ -5350,9 +5661,12 @@ async function runRefresh(opts = {}) {
|
|
|
5350
5661
|
let updatedAt = base?.updatedAt ?? now.toISOString();
|
|
5351
5662
|
let fixtures = base?.fixtures;
|
|
5352
5663
|
let fixturesUpdatedAt = base?.fixturesUpdatedAt;
|
|
5664
|
+
let fixturesAttemptedAt = base?.fixturesAttemptedAt;
|
|
5665
|
+
let backoffUntil = base?.backoffUntil;
|
|
5666
|
+
const adapter = liveAdapter(source);
|
|
5353
5667
|
if (needLive) {
|
|
5354
5668
|
try {
|
|
5355
|
-
const r = await getLiveMatches(
|
|
5669
|
+
const r = await getLiveMatches(adapter, now);
|
|
5356
5670
|
live = r.matches;
|
|
5357
5671
|
degraded = r.degraded;
|
|
5358
5672
|
} catch {
|
|
@@ -5361,8 +5675,9 @@ async function runRefresh(opts = {}) {
|
|
|
5361
5675
|
updatedAt = now.toISOString();
|
|
5362
5676
|
}
|
|
5363
5677
|
if (needFixtures) {
|
|
5678
|
+
fixturesAttemptedAt = now.toISOString();
|
|
5364
5679
|
try {
|
|
5365
|
-
const r = await getKnockoutFixtures(
|
|
5680
|
+
const r = await getKnockoutFixtures(adapter, now);
|
|
5366
5681
|
if (!r.degraded) {
|
|
5367
5682
|
fixtures = r.fixtures;
|
|
5368
5683
|
fixturesUpdatedAt = now.toISOString();
|
|
@@ -5370,6 +5685,13 @@ async function runRefresh(opts = {}) {
|
|
|
5370
5685
|
} catch {
|
|
5371
5686
|
}
|
|
5372
5687
|
}
|
|
5688
|
+
if (adapter.lastError?.throttled) {
|
|
5689
|
+
backoffUntil = new Date(
|
|
5690
|
+
nowMs + BACKOFF_MS + Math.floor(Math.random() * BACKOFF_JITTER_MS)
|
|
5691
|
+
).toISOString();
|
|
5692
|
+
} else if (backoffUntil && Date.parse(backoffUntil) <= nowMs) {
|
|
5693
|
+
backoffUntil = void 0;
|
|
5694
|
+
}
|
|
5373
5695
|
writeState({
|
|
5374
5696
|
updatedAt,
|
|
5375
5697
|
live,
|
|
@@ -5377,19 +5699,23 @@ async function runRefresh(opts = {}) {
|
|
|
5377
5699
|
source,
|
|
5378
5700
|
competition,
|
|
5379
5701
|
...fixtures ? { fixtures } : {},
|
|
5380
|
-
...fixturesUpdatedAt ? { fixturesUpdatedAt } : {}
|
|
5702
|
+
...fixturesUpdatedAt ? { fixturesUpdatedAt } : {},
|
|
5703
|
+
...fixturesAttemptedAt ? { fixturesAttemptedAt } : {},
|
|
5704
|
+
...backoffUntil ? { backoffUntil } : {}
|
|
5381
5705
|
});
|
|
5382
5706
|
} finally {
|
|
5383
5707
|
releaseLock();
|
|
5384
5708
|
}
|
|
5385
5709
|
}
|
|
5386
|
-
function shouldRefresh(now = Date.now()) {
|
|
5710
|
+
function shouldRefresh(now = Date.now(), state = readState("espn", resolveCompetition())) {
|
|
5387
5711
|
if (!liveWindowActive(now)) return false;
|
|
5712
|
+
if (backoffActive(state, now)) return false;
|
|
5388
5713
|
if (isLockFresh(now)) return false;
|
|
5389
|
-
return ageMs(
|
|
5714
|
+
return ageMs(state, now) > LIVE_TTL_MS;
|
|
5390
5715
|
}
|
|
5391
|
-
function shouldRefreshFixtures(now = Date.now(), state = readState()) {
|
|
5716
|
+
function shouldRefreshFixtures(now = Date.now(), state = readState("espn", resolveCompetition())) {
|
|
5392
5717
|
if (!inKnockoutPhase(now)) return false;
|
|
5718
|
+
if (backoffActive(state, now)) return false;
|
|
5393
5719
|
if (isLockFresh(now)) return false;
|
|
5394
5720
|
return fixturesStale(state, now);
|
|
5395
5721
|
}
|
|
@@ -5399,81 +5725,25 @@ function spawnRefresh(source) {
|
|
|
5399
5725
|
if (!entry) return;
|
|
5400
5726
|
const child = spawn(process.execPath, [entry, "_refresh", "--source", source], {
|
|
5401
5727
|
detached: true,
|
|
5402
|
-
stdio: "ignore"
|
|
5728
|
+
stdio: "ignore",
|
|
5729
|
+
// Windows: a detached child gets its own console window without this โ
|
|
5730
|
+
// the statusline would flash one every ~12โ15s during live matches.
|
|
5731
|
+
windowsHide: true
|
|
5403
5732
|
});
|
|
5404
5733
|
child.unref();
|
|
5405
5734
|
} catch {
|
|
5406
5735
|
}
|
|
5407
5736
|
}
|
|
5408
5737
|
|
|
5409
|
-
// src/cursorPayload.ts
|
|
5410
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
5411
|
-
function parseCursorPayload(raw) {
|
|
5412
|
-
try {
|
|
5413
|
-
const trimmed = raw.trim();
|
|
5414
|
-
if (!trimmed) return void 0;
|
|
5415
|
-
return JSON.parse(trimmed);
|
|
5416
|
-
} catch {
|
|
5417
|
-
return void 0;
|
|
5418
|
-
}
|
|
5419
|
-
}
|
|
5420
|
-
function readCursorPayload() {
|
|
5421
|
-
try {
|
|
5422
|
-
if (process.stdin.isTTY) return void 0;
|
|
5423
|
-
return parseCursorPayload(readFileSync4(0, "utf8"));
|
|
5424
|
-
} catch {
|
|
5425
|
-
return void 0;
|
|
5426
|
-
}
|
|
5427
|
-
}
|
|
5428
|
-
function looksLikeCursorPayload(payload) {
|
|
5429
|
-
return !!(payload.model?.display_name || payload.context_window != null || payload.render_width_chars != null || payload.worktree?.name || payload.vim?.mode);
|
|
5430
|
-
}
|
|
5431
|
-
function cursorMetaEnabled(payload) {
|
|
5432
|
-
const v = (process.env.CLAUDINHO_CURSOR_META ?? "").toLowerCase();
|
|
5433
|
-
if (v === "0" || v === "false" || v === "no") return false;
|
|
5434
|
-
if (v === "1" || v === "true" || v === "yes") return !!payload;
|
|
5435
|
-
if (v === "auto") return !!payload && looksLikeCursorPayload(payload);
|
|
5436
|
-
return false;
|
|
5437
|
-
}
|
|
5438
|
-
function renderCursorMetaLine(payload) {
|
|
5439
|
-
const parts = [];
|
|
5440
|
-
const model = payload.model?.display_name;
|
|
5441
|
-
if (model) {
|
|
5442
|
-
let label = model;
|
|
5443
|
-
if (payload.model?.param_summary) label += ` ${payload.model.param_summary}`;
|
|
5444
|
-
parts.push(label);
|
|
5445
|
-
}
|
|
5446
|
-
const pct2 = payload.context_window?.used_percentage;
|
|
5447
|
-
if (typeof pct2 === "number" && Number.isFinite(pct2)) {
|
|
5448
|
-
parts.push(`ctx ${Math.floor(pct2)}%`);
|
|
5449
|
-
}
|
|
5450
|
-
if (payload.worktree?.name) parts.push(`wt ${payload.worktree.name}`);
|
|
5451
|
-
if (payload.vim?.mode) parts.push(payload.vim.mode);
|
|
5452
|
-
if (parts.length === 0) return void 0;
|
|
5453
|
-
return `\x1B[90m${parts.join(" ")}\x1B[0m`;
|
|
5454
|
-
}
|
|
5455
|
-
function renderPromptOutput(scoreLine, payload) {
|
|
5456
|
-
const meta = payload && cursorMetaEnabled(payload) ? renderCursorMetaLine(payload) : void 0;
|
|
5457
|
-
if (meta) return `${scoreLine}
|
|
5458
|
-
${meta}`;
|
|
5459
|
-
return scoreLine;
|
|
5460
|
-
}
|
|
5461
|
-
|
|
5462
5738
|
// src/install.ts
|
|
5463
|
-
import {
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
mkdirSync as mkdirSync4,
|
|
5467
|
-
readFileSync as readFileSync5,
|
|
5468
|
-
writeFileSync as writeFileSync3
|
|
5469
|
-
} from "fs";
|
|
5470
|
-
import { homedir as homedir4 } from "os";
|
|
5471
|
-
import { dirname as dirname2, join as join4 } from "path";
|
|
5739
|
+
import { copyFileSync, existsSync, readFileSync as readFileSync5 } from "fs";
|
|
5740
|
+
import { homedir as homedir2 } from "os";
|
|
5741
|
+
import { join as join5 } from "path";
|
|
5472
5742
|
function claudeSettingsPath() {
|
|
5473
|
-
return
|
|
5743
|
+
return join5(homedir2(), ".claude", "settings.json");
|
|
5474
5744
|
}
|
|
5475
5745
|
function cursorCliConfigPath() {
|
|
5476
|
-
return
|
|
5746
|
+
return join5(homedir2(), ".cursor", "cli-config.json");
|
|
5477
5747
|
}
|
|
5478
5748
|
function isSameCommand(configured, requested) {
|
|
5479
5749
|
return configured === requested;
|
|
@@ -5535,8 +5805,7 @@ function initStatuslineFor(target, opts = {}) {
|
|
|
5535
5805
|
}
|
|
5536
5806
|
backupOnce(path);
|
|
5537
5807
|
settings.statusLine = sl;
|
|
5538
|
-
|
|
5539
|
-
writeFileSync3(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
5808
|
+
writeFileAtomic(path, JSON.stringify(settings, null, 2) + "\n");
|
|
5540
5809
|
const surface = target === "cursor" ? "Cursor CLI statusline" : "Statusline";
|
|
5541
5810
|
return {
|
|
5542
5811
|
action: "written",
|
|
@@ -5582,8 +5851,7 @@ function initHook(opts = {}) {
|
|
|
5582
5851
|
const hooks = settings.hooks;
|
|
5583
5852
|
hooks[CLAUDE_HOOK_EVENT] ??= [];
|
|
5584
5853
|
hooks[CLAUDE_HOOK_EVENT].push({ hooks: [{ type: "command", command }] });
|
|
5585
|
-
|
|
5586
|
-
writeFileSync3(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
5854
|
+
writeFileAtomic(path, JSON.stringify(settings, null, 2) + "\n");
|
|
5587
5855
|
return {
|
|
5588
5856
|
action: "written",
|
|
5589
5857
|
path,
|
|
@@ -5654,6 +5922,11 @@ function precheck(cfg, t2, date) {
|
|
|
5654
5922
|
if (cfg.tz && !isValidTimeZone(cfg.tz)) {
|
|
5655
5923
|
process.stderr.write(t2("warn.tz", { tz: cfg.tz }) + "\n");
|
|
5656
5924
|
}
|
|
5925
|
+
if (!KNOWN_SOURCES.includes(cfg.source)) {
|
|
5926
|
+
throw new InputError(
|
|
5927
|
+
t2("err.source", { source: cfg.source, sources: KNOWN_SOURCES.join(", ") })
|
|
5928
|
+
);
|
|
5929
|
+
}
|
|
5657
5930
|
const competition = resolveCompetition();
|
|
5658
5931
|
if (competition !== DEFAULT_COMPETITION) {
|
|
5659
5932
|
process.stderr.write(
|
|
@@ -5665,18 +5938,24 @@ function precheck(cfg, t2, date) {
|
|
|
5665
5938
|
throw new InputError(t2("err.date", { date }));
|
|
5666
5939
|
}
|
|
5667
5940
|
}
|
|
5668
|
-
function resolveTeamArg(team, usage) {
|
|
5941
|
+
function resolveTeamArg(team, usage, t2) {
|
|
5669
5942
|
const raw = team ?? process.env.CLAUDINHO_TEAM;
|
|
5670
5943
|
if (!raw) throw new InputError(usage);
|
|
5671
5944
|
const { team: hit, matches } = lookupTeam(raw);
|
|
5672
5945
|
if (hit) return hit.code;
|
|
5673
5946
|
if (matches.length > 1) {
|
|
5674
5947
|
throw new InputError(
|
|
5675
|
-
|
|
5948
|
+
`${t2("team.ambiguous", { query: raw })} ${matches.map((m) => `${m.name} (${m.code})`).join(", ")}`
|
|
5676
5949
|
);
|
|
5677
5950
|
}
|
|
5678
5951
|
if (/^[A-Za-z]{3}$/.test(raw)) return raw.toUpperCase();
|
|
5679
|
-
throw new InputError(
|
|
5952
|
+
throw new InputError(t2("team.none", { query: raw }));
|
|
5953
|
+
}
|
|
5954
|
+
function resolveEnvTeam(raw) {
|
|
5955
|
+
if (!raw) return void 0;
|
|
5956
|
+
const { team } = lookupTeam(raw);
|
|
5957
|
+
if (team) return team.code;
|
|
5958
|
+
return /^[A-Za-z]{3}$/.test(raw) ? raw.toUpperCase() : void 0;
|
|
5680
5959
|
}
|
|
5681
5960
|
async function cmdToday(date, ctx) {
|
|
5682
5961
|
const { cfg, t: t2 } = ctx;
|
|
@@ -5748,7 +6027,7 @@ async function cmdLive(ctx) {
|
|
|
5748
6027
|
async function cmdNext(team, ctx) {
|
|
5749
6028
|
const { cfg, t: t2, now } = ctx;
|
|
5750
6029
|
precheck(cfg, t2);
|
|
5751
|
-
const code = resolveTeamArg(team, "Usage: claudinho next <team> (or set CLAUDINHO_TEAM)");
|
|
6030
|
+
const code = resolveTeamArg(team, "Usage: claudinho next <team> (or set CLAUDINHO_TEAM)", t2);
|
|
5752
6031
|
const { fixture, degraded, source } = await getNextFixtureForTeam(
|
|
5753
6032
|
adapterFor(ctx),
|
|
5754
6033
|
code,
|
|
@@ -5770,7 +6049,7 @@ async function cmdNext(team, ctx) {
|
|
|
5770
6049
|
out(header(t2("next.label", { team: code }), c));
|
|
5771
6050
|
out();
|
|
5772
6051
|
out(matchLine(fixture, cfg, t2, c, flags));
|
|
5773
|
-
const stage = fixture.stage !== "GROUP" ? `${
|
|
6052
|
+
const stage = fixture.stage !== "GROUP" ? `${stageLabelI18n(cfg.lang, fixture.stage)} \xB7 ` : "";
|
|
5774
6053
|
out(
|
|
5775
6054
|
" " + c.dim(
|
|
5776
6055
|
`${stage}${formatKickoff(fixture.kickoff, { tz: cfg.tz, locale: cfg.lang })} \xB7 ` + t2("next.in", { countdown: countdown(fixture.kickoff) })
|
|
@@ -5928,10 +6207,10 @@ async function cmdBracket(stage, opts, ctx) {
|
|
|
5928
6207
|
out(disclaimer(t2, c));
|
|
5929
6208
|
maybeStarNudge(ctx);
|
|
5930
6209
|
}
|
|
5931
|
-
function cmdPrompt({ cfg }) {
|
|
6210
|
+
function cmdPrompt({ cfg }, io = {}) {
|
|
5932
6211
|
try {
|
|
5933
|
-
const payload = readCursorPayload();
|
|
5934
|
-
const team = process.env.CLAUDINHO_TEAM;
|
|
6212
|
+
const payload = "cursor" in io ? io.cursor : readCursorPayload();
|
|
6213
|
+
const team = resolveEnvTeam(process.env.CLAUDINHO_TEAM);
|
|
5935
6214
|
const compact = !["0", "false", "no"].includes(
|
|
5936
6215
|
(process.env.CLAUDINHO_COMPACT ?? "").toLowerCase()
|
|
5937
6216
|
);
|
|
@@ -5940,7 +6219,7 @@ function cmdPrompt({ cfg }) {
|
|
|
5940
6219
|
const state = readCurrentState(cfg.source, resolveCompetition());
|
|
5941
6220
|
const scoreLine = renderPrompt(state, { team, compact, max, flags: flagsEnabled() });
|
|
5942
6221
|
out(renderPromptOutput(scoreLine, payload));
|
|
5943
|
-
if (!state || shouldRefresh() || shouldRefreshFixtures(Date.now(), state)) {
|
|
6222
|
+
if (!state && !isLockFresh() || shouldRefresh(Date.now(), state) || shouldRefreshFixtures(Date.now(), state)) {
|
|
5944
6223
|
spawnRefresh(cfg.source);
|
|
5945
6224
|
}
|
|
5946
6225
|
} catch {
|
|
@@ -5949,11 +6228,11 @@ function cmdPrompt({ cfg }) {
|
|
|
5949
6228
|
}
|
|
5950
6229
|
function cmdHook({ cfg }) {
|
|
5951
6230
|
try {
|
|
5952
|
-
const team = process.env.CLAUDINHO_TEAM;
|
|
6231
|
+
const team = resolveEnvTeam(process.env.CLAUDINHO_TEAM);
|
|
5953
6232
|
const state = readCurrentState(cfg.source, resolveCompetition());
|
|
5954
6233
|
const ctx = renderHook(state, { team, flags: flagsEnabled() });
|
|
5955
6234
|
if (ctx) out(ctx);
|
|
5956
|
-
if (!state || shouldRefresh() || shouldRefreshFixtures(Date.now(), state)) {
|
|
6235
|
+
if (!state && !isLockFresh() || shouldRefresh(Date.now(), state) || shouldRefreshFixtures(Date.now(), state)) {
|
|
5957
6236
|
spawnRefresh(cfg.source);
|
|
5958
6237
|
}
|
|
5959
6238
|
} catch {
|
|
@@ -6001,7 +6280,8 @@ function cmdInitCursor(opts, { cfg }) {
|
|
|
6001
6280
|
out(CURSOR_MCP_SNIPPET);
|
|
6002
6281
|
return;
|
|
6003
6282
|
}
|
|
6004
|
-
|
|
6283
|
+
const res = initCursorStatusline();
|
|
6284
|
+
printInitResult(res, cfg);
|
|
6005
6285
|
out("");
|
|
6006
6286
|
out("Optional \u2014 live MCP tools in Cursor: add to ~/.cursor/mcp.json (or project .cursor/mcp.json):");
|
|
6007
6287
|
out(CURSOR_MCP_SNIPPET);
|
|
@@ -6009,7 +6289,7 @@ function cmdInitCursor(opts, { cfg }) {
|
|
|
6009
6289
|
out("Tip: export CLAUDINHO_CURSOR_META=auto for a model + context line below the score.");
|
|
6010
6290
|
out("");
|
|
6011
6291
|
out("\u2192 Restart your agent session to see it.");
|
|
6012
|
-
printInitStarCta(cfg);
|
|
6292
|
+
if (res.action === "written") printInitStarCta(cfg);
|
|
6013
6293
|
}
|
|
6014
6294
|
function cmdInitClaude(opts, { cfg }) {
|
|
6015
6295
|
if (opts.print) {
|
|
@@ -6023,14 +6303,16 @@ function cmdInitClaude(opts, { cfg }) {
|
|
|
6023
6303
|
out(CLAUDE_MCP_ONELINER);
|
|
6024
6304
|
return;
|
|
6025
6305
|
}
|
|
6026
|
-
|
|
6027
|
-
|
|
6306
|
+
const statusRes = initStatusline();
|
|
6307
|
+
const hookRes = initHook();
|
|
6308
|
+
printInitResult(statusRes, cfg);
|
|
6309
|
+
printInitResult(hookRes, cfg);
|
|
6028
6310
|
out("");
|
|
6029
6311
|
out("Next \u2014 add the MCP server:");
|
|
6030
6312
|
out(` ${CLAUDE_MCP_ONELINER}`);
|
|
6031
6313
|
out("");
|
|
6032
6314
|
out("\u2192 Restart Claude Code to see it.");
|
|
6033
|
-
printInitStarCta(cfg);
|
|
6315
|
+
if (statusRes.action === "written" || hookRes.action === "written") printInitStarCta(cfg);
|
|
6034
6316
|
}
|
|
6035
6317
|
async function cmdMatch(id, ctx) {
|
|
6036
6318
|
const { cfg, t: t2 } = ctx;
|
|
@@ -6054,7 +6336,7 @@ async function cmdMatch(id, ctx) {
|
|
|
6054
6336
|
out(disclaimer(t2, c));
|
|
6055
6337
|
return;
|
|
6056
6338
|
}
|
|
6057
|
-
const stageLabelText =
|
|
6339
|
+
const stageLabelText = stageLabelI18n(cfg.lang, match.stage, match.group ?? void 0);
|
|
6058
6340
|
out(header(`${match.home.name} ${scoreline(match)} ${match.away.name}`, c));
|
|
6059
6341
|
out(" " + c.dim(`${stageLabelText} \xB7 ${matchLocation(match)}`));
|
|
6060
6342
|
out(
|
|
@@ -6100,7 +6382,7 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
6100
6382
|
const { cfg, t: t2 } = ctx;
|
|
6101
6383
|
if (target === "next") {
|
|
6102
6384
|
precheck(cfg, t2);
|
|
6103
|
-
const code = resolveTeamArg(team, "Usage: claudinho markets next <team> (or set CLAUDINHO_TEAM)");
|
|
6385
|
+
const code = resolveTeamArg(team, "Usage: claudinho markets next <team> (or set CLAUDINHO_TEAM)", t2);
|
|
6104
6386
|
const now2 = ctx.now ?? /* @__PURE__ */ new Date();
|
|
6105
6387
|
const { match: fixture, degraded } = await marketFixtureForTeam(adapterFor(ctx), code, now2);
|
|
6106
6388
|
const sig = fixture && marketRelevant(fixture, now2) ? (await marketSignalsFor(ctx, [fixture], MARKETS_CMD_OPTS)).get(fixture.id) : void 0;
|
|
@@ -6376,7 +6658,7 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
6376
6658
|
}
|
|
6377
6659
|
if (target === "next") {
|
|
6378
6660
|
precheck(cfg, t2);
|
|
6379
|
-
const code = resolveTeamArg(team, "Usage: claudinho share next <team> (or set CLAUDINHO_TEAM)");
|
|
6661
|
+
const code = resolveTeamArg(team, "Usage: claudinho share next <team> (or set CLAUDINHO_TEAM)", t2);
|
|
6380
6662
|
const { fixture, degraded: degraded2, source: source2 } = await getNextFixtureForTeam(
|
|
6381
6663
|
adapterFor(ctx),
|
|
6382
6664
|
code,
|
|
@@ -6528,6 +6810,7 @@ function maybeStarNudge(ctx) {
|
|
|
6528
6810
|
out(c.dim(` \u2B50 Enjoying Claudinho? Star it \u2192 ${REPO_URL} (claudinho star)`));
|
|
6529
6811
|
}
|
|
6530
6812
|
function printInitStarCta(cfg) {
|
|
6813
|
+
if (cfg.json || !process.stdout.isTTY || process.env.CLAUDINHO_NO_STAR) return;
|
|
6531
6814
|
const c = painterFor(cfg);
|
|
6532
6815
|
out("");
|
|
6533
6816
|
out(c.dim(`\u2B50 If this keeps you in the flow during the match, star the repo \u2192 ${REPO_URL}`));
|
|
@@ -6541,7 +6824,8 @@ function cmdVibe(ctx) {
|
|
|
6541
6824
|
const state = readCurrentState(cfg.source, resolveCompetition());
|
|
6542
6825
|
liveSeg = vibeLiveSegment(
|
|
6543
6826
|
liveMatchesFromCache(state, (ctx.now ?? /* @__PURE__ */ new Date()).getTime()),
|
|
6544
|
-
|
|
6827
|
+
// Name-or-code, matching the statusline/hook (offline lookup).
|
|
6828
|
+
resolveEnvTeam(process.env.CLAUDINHO_TEAM)
|
|
6545
6829
|
);
|
|
6546
6830
|
} catch {
|
|
6547
6831
|
}
|
|
@@ -6565,7 +6849,7 @@ function handlePipeError(stream) {
|
|
|
6565
6849
|
}
|
|
6566
6850
|
handlePipeError(process.stdout);
|
|
6567
6851
|
handlePipeError(process.stderr);
|
|
6568
|
-
var VERSION = "0.
|
|
6852
|
+
var VERSION = "0.9.0";
|
|
6569
6853
|
var DISCLAIMER = "Claudinho is an independent fan project. Not affiliated with or endorsed by FIFA or Anthropic.";
|
|
6570
6854
|
function ctxFrom(cmd) {
|
|
6571
6855
|
let root = cmd;
|
|
@@ -6651,8 +6935,9 @@ program.command("share").description("print a shareable, copy-pasteable match sn
|
|
|
6651
6935
|
fail(e);
|
|
6652
6936
|
}
|
|
6653
6937
|
});
|
|
6654
|
-
program.command("prompt").description("print a status line (Claude Code / Cursor CLI statusline, tmux, Starship, \u2026)").action((_opts, cmd) => {
|
|
6655
|
-
|
|
6938
|
+
program.command("prompt").description("print a status line (Claude Code / Cursor CLI statusline, tmux, Starship, \u2026)").action(async (_opts, cmd) => {
|
|
6939
|
+
const cursor = await readCursorPayloadBounded();
|
|
6940
|
+
cmdPrompt(ctxFrom(cmd), { cursor });
|
|
6656
6941
|
});
|
|
6657
6942
|
var init = program.command("init").description("one-step setup for your agent: `init cursor` or `init claude`");
|
|
6658
6943
|
init.command("cursor").description("set up claudinho for the Cursor CLI (statusline + MCP config)").option("--print", "print the config snippets for manual install instead of writing them").action((opts, cmd) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claudinho/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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, Claude Code, and Cursor CLI statusline. No API keys. Not affiliated with FIFA or Anthropic.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -58,10 +58,11 @@
|
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "^22.20.0",
|
|
61
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
61
62
|
"tsup": "^8.0.0",
|
|
62
63
|
"typescript": "^5.7.0",
|
|
63
64
|
"vitest": "^4.1.9",
|
|
64
|
-
"@claudinho/core": "0.
|
|
65
|
+
"@claudinho/core": "0.9.0"
|
|
65
66
|
},
|
|
66
67
|
"scripts": {
|
|
67
68
|
"build": "tsup",
|