@claudinho/cli 0.11.1 → 0.12.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 +326 -249
- package/dist/index.js +1085 -200
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -335,6 +335,13 @@ var EN = {
|
|
|
335
335
|
"status.fullTime": "full-time",
|
|
336
336
|
"status.postponed": "postponed",
|
|
337
337
|
"status.cancelled": "cancelled",
|
|
338
|
+
// A timeline row's kind word (core `timelineRows`).
|
|
339
|
+
"event.goal": "Goal",
|
|
340
|
+
"event.ownGoal": "Own goal",
|
|
341
|
+
"event.penalty": "Penalty",
|
|
342
|
+
"event.yellow": "Yellow card",
|
|
343
|
+
"event.red": "Red card",
|
|
344
|
+
"event.sub": "Substitution",
|
|
338
345
|
"live.title": "Live now",
|
|
339
346
|
"today.title": "Today's matches",
|
|
340
347
|
"today.on": "Matches",
|
|
@@ -343,6 +350,23 @@ var EN = {
|
|
|
343
350
|
"next.in": "in {countdown}",
|
|
344
351
|
// A kickoff that has passed, where the countdown would say "in" (core `countdownPhrase`).
|
|
345
352
|
"countdown.now": "now",
|
|
353
|
+
// The ambient line's syncing phrase (`claudinho ambient --json` alone; the
|
|
354
|
+
// statusline's own `prompt` stays English).
|
|
355
|
+
"ambient.syncing": "live \xB7 syncing\u2026",
|
|
356
|
+
// The plugin's moment words, sent by `claudinho ambient --json` as `words`
|
|
357
|
+
// (`{team}` a template the plugin fills with the record's own names). Each
|
|
358
|
+
// at most 40 characters; `{team}` once in `moment.goalFor` and `moment.win`
|
|
359
|
+
// and nowhere else, or the plugin falls back to its English words.
|
|
360
|
+
"moment.goal": "GOAL!",
|
|
361
|
+
"moment.ownGoal": "Own goal",
|
|
362
|
+
"moment.ownGoalDetail": "own goal",
|
|
363
|
+
"moment.goalFor": "Goal for {team}",
|
|
364
|
+
"moment.goals": "Goals",
|
|
365
|
+
"moment.scoreUpdated": "Score updated",
|
|
366
|
+
"moment.penalties": "Penalties",
|
|
367
|
+
"moment.fullTime": "Full time",
|
|
368
|
+
"moment.win": "{team} win",
|
|
369
|
+
"moment.draw": "Draw",
|
|
346
370
|
// A title that introduces the lines below it (the MCP text).
|
|
347
371
|
heading: "{title}:",
|
|
348
372
|
// A list's empty state and the outage lines: the CLI's sentences, the MCP
|
|
@@ -354,6 +378,7 @@ var EN = {
|
|
|
354
378
|
"feed.earlierRecord": "Live state could not be refreshed \u2014 showing the provider's earlier record.",
|
|
355
379
|
"next.none": "No upcoming fixture found for {team}.",
|
|
356
380
|
"match.none": "No match found with id {id}.",
|
|
381
|
+
"match.kickoff": "Kickoff",
|
|
357
382
|
// The MCP text's own sentences for these states (the CLI says `live.degraded`
|
|
358
383
|
// there): they keep the team or the id the answer is about.
|
|
359
384
|
"next.unreachable": "Couldn't reach the data provider \u2014 no upcoming fixture confirmed for {team}.",
|
|
@@ -431,6 +456,12 @@ var ES = {
|
|
|
431
456
|
"status.fullTime": "finalizado",
|
|
432
457
|
"status.postponed": "aplazado",
|
|
433
458
|
"status.cancelled": "cancelado",
|
|
459
|
+
"event.goal": "Gol",
|
|
460
|
+
"event.ownGoal": "Gol en contra",
|
|
461
|
+
"event.penalty": "Penal",
|
|
462
|
+
"event.yellow": "Tarjeta amarilla",
|
|
463
|
+
"event.red": "Tarjeta roja",
|
|
464
|
+
"event.sub": "Cambio",
|
|
434
465
|
"live.title": "En vivo",
|
|
435
466
|
"today.title": "Partidos de hoy",
|
|
436
467
|
"today.on": "Partidos",
|
|
@@ -438,6 +469,17 @@ var ES = {
|
|
|
438
469
|
"next.label": "Pr\xF3ximo partido de {team}",
|
|
439
470
|
"next.in": "en {countdown}",
|
|
440
471
|
"countdown.now": "ahora",
|
|
472
|
+
"ambient.syncing": "en vivo \xB7 sincronizando\u2026",
|
|
473
|
+
"moment.goal": "\xA1GOL!",
|
|
474
|
+
"moment.ownGoal": "Gol en contra",
|
|
475
|
+
"moment.ownGoalDetail": "gol en contra",
|
|
476
|
+
"moment.goalFor": "Gol de {team}",
|
|
477
|
+
"moment.goals": "Goles",
|
|
478
|
+
"moment.scoreUpdated": "Marcador corregido",
|
|
479
|
+
"moment.penalties": "Penales",
|
|
480
|
+
"moment.fullTime": "Final",
|
|
481
|
+
"moment.win": "Gana {team}",
|
|
482
|
+
"moment.draw": "Empate",
|
|
441
483
|
heading: "{title}:",
|
|
442
484
|
"today.none": "No hay partidos para esta fecha.",
|
|
443
485
|
"live.none": "No hay partidos en juego ahora mismo.",
|
|
@@ -446,6 +488,7 @@ var ES = {
|
|
|
446
488
|
"feed.earlierRecord": "No se pudo actualizar el estado en vivo \u2014 se muestra el registro anterior del proveedor.",
|
|
447
489
|
"next.none": "No se encontr\xF3 pr\xF3ximo partido para {team}.",
|
|
448
490
|
"match.none": "No se encontr\xF3 partido con id {id}.",
|
|
491
|
+
"match.kickoff": "Inicio",
|
|
449
492
|
"next.unreachable": "No se pudo contactar al proveedor de datos \u2014 ning\xFAn pr\xF3ximo partido confirmado para {team}.",
|
|
450
493
|
"match.unreachable": "No se pudo contactar al proveedor de datos \u2014 no se pudo consultar el partido {id}.",
|
|
451
494
|
"team.ambiguous": '"{query}" es ambiguo. \xBFQuisiste decir:',
|
|
@@ -520,6 +563,12 @@ var PT = {
|
|
|
520
563
|
"status.fullTime": "encerrado",
|
|
521
564
|
"status.postponed": "adiado",
|
|
522
565
|
"status.cancelled": "cancelado",
|
|
566
|
+
"event.goal": "Gol",
|
|
567
|
+
"event.ownGoal": "Gol contra",
|
|
568
|
+
"event.penalty": "P\xEAnalti",
|
|
569
|
+
"event.yellow": "Cart\xE3o amarelo",
|
|
570
|
+
"event.red": "Cart\xE3o vermelho",
|
|
571
|
+
"event.sub": "Substitui\xE7\xE3o",
|
|
523
572
|
"live.title": "Ao vivo",
|
|
524
573
|
"today.title": "Jogos de hoje",
|
|
525
574
|
"today.on": "Jogos",
|
|
@@ -527,6 +576,17 @@ var PT = {
|
|
|
527
576
|
"next.label": "Pr\xF3ximo jogo de {team}",
|
|
528
577
|
"next.in": "em {countdown}",
|
|
529
578
|
"countdown.now": "agora",
|
|
579
|
+
"ambient.syncing": "ao vivo \xB7 sincronizando\u2026",
|
|
580
|
+
"moment.goal": "GOL!",
|
|
581
|
+
"moment.ownGoal": "Gol contra",
|
|
582
|
+
"moment.ownGoalDetail": "gol contra",
|
|
583
|
+
"moment.goalFor": "Gol \xB7 {team}",
|
|
584
|
+
"moment.goals": "Gols",
|
|
585
|
+
"moment.scoreUpdated": "Placar corrigido",
|
|
586
|
+
"moment.penalties": "P\xEAnaltis",
|
|
587
|
+
"moment.fullTime": "Fim de jogo",
|
|
588
|
+
"moment.win": "{team} vence",
|
|
589
|
+
"moment.draw": "Empate",
|
|
530
590
|
heading: "{title}:",
|
|
531
591
|
"today.none": "Nenhum jogo para esta data.",
|
|
532
592
|
"live.none": "Nenhum jogo em andamento agora.",
|
|
@@ -535,6 +595,7 @@ var PT = {
|
|
|
535
595
|
"feed.earlierRecord": "N\xE3o foi poss\xEDvel atualizar o estado ao vivo \u2014 mostrando o registro anterior do provedor.",
|
|
536
596
|
"next.none": "Nenhum pr\xF3ximo jogo encontrado para {team}.",
|
|
537
597
|
"match.none": "Nenhum jogo encontrado com id {id}.",
|
|
598
|
+
"match.kickoff": "In\xEDcio",
|
|
538
599
|
"next.unreachable": "N\xE3o foi poss\xEDvel contatar o provedor de dados \u2014 nenhum pr\xF3ximo jogo confirmado para {team}.",
|
|
539
600
|
"match.unreachable": "N\xE3o foi poss\xEDvel contatar o provedor de dados \u2014 o jogo {id} n\xE3o p\xF4de ser consultado.",
|
|
540
601
|
"team.ambiguous": '"{query}" \xE9 amb\xEDguo. Voc\xEA quis dizer:',
|
|
@@ -609,6 +670,12 @@ var FR = {
|
|
|
609
670
|
"status.fullTime": "termin\xE9",
|
|
610
671
|
"status.postponed": "report\xE9",
|
|
611
672
|
"status.cancelled": "annul\xE9",
|
|
673
|
+
"event.goal": "But",
|
|
674
|
+
"event.ownGoal": "But contre son camp",
|
|
675
|
+
"event.penalty": "Penalty",
|
|
676
|
+
"event.yellow": "Carton jaune",
|
|
677
|
+
"event.red": "Carton rouge",
|
|
678
|
+
"event.sub": "Remplacement",
|
|
612
679
|
"live.title": "En direct",
|
|
613
680
|
"today.title": "Matchs d'aujourd'hui",
|
|
614
681
|
"today.on": "Matchs",
|
|
@@ -616,6 +683,17 @@ var FR = {
|
|
|
616
683
|
"next.label": "Prochain match de {team}",
|
|
617
684
|
"next.in": "dans {countdown}",
|
|
618
685
|
"countdown.now": "maintenant",
|
|
686
|
+
"ambient.syncing": "en direct \xB7 synchronisation\u2026",
|
|
687
|
+
"moment.goal": "BUT !",
|
|
688
|
+
"moment.ownGoal": "But contre son camp",
|
|
689
|
+
"moment.ownGoalDetail": "contre son camp",
|
|
690
|
+
"moment.goalFor": "But pour {team}",
|
|
691
|
+
"moment.goals": "Buts",
|
|
692
|
+
"moment.scoreUpdated": "Score corrig\xE9",
|
|
693
|
+
"moment.penalties": "Tirs au but",
|
|
694
|
+
"moment.fullTime": "Fin du match",
|
|
695
|
+
"moment.win": "{team} gagne",
|
|
696
|
+
"moment.draw": "Match nul",
|
|
619
697
|
heading: "{title} :",
|
|
620
698
|
"today.none": "Aucun match pr\xE9vu pour cette date.",
|
|
621
699
|
"live.none": "Aucun match en cours pour l'instant.",
|
|
@@ -624,6 +702,7 @@ var FR = {
|
|
|
624
702
|
"feed.earlierRecord": "L\u2019\xE9tat en direct n\u2019a pas pu \xEAtre actualis\xE9 \u2014 affichage de l\u2019enregistrement pr\xE9c\xE9dent du fournisseur.",
|
|
625
703
|
"next.none": "Aucun prochain match trouv\xE9 pour {team}.",
|
|
626
704
|
"match.none": "Aucun match trouv\xE9 avec id {id}.",
|
|
705
|
+
"match.kickoff": "Coup d\u2019envoi",
|
|
627
706
|
"next.unreachable": "Impossible de joindre le fournisseur de donn\xE9es \u2014 aucun prochain match confirm\xE9 pour {team}.",
|
|
628
707
|
"match.unreachable": "Impossible de joindre le fournisseur de donn\xE9es \u2014 le match {id} n\u2019a pas pu \xEAtre consult\xE9.",
|
|
629
708
|
"team.ambiguous": '"{query}" est ambigu. Vouliez-vous dire :',
|
|
@@ -657,6 +736,20 @@ function t(lang, key, vars) {
|
|
|
657
736
|
(whole, name) => Object.hasOwn(vars, name) ? vars[name] : whole
|
|
658
737
|
);
|
|
659
738
|
}
|
|
739
|
+
function momentWords(lang) {
|
|
740
|
+
return {
|
|
741
|
+
goal: t(lang, "moment.goal"),
|
|
742
|
+
ownGoal: t(lang, "moment.ownGoal"),
|
|
743
|
+
ownGoalDetail: t(lang, "moment.ownGoalDetail"),
|
|
744
|
+
goalFor: t(lang, "moment.goalFor"),
|
|
745
|
+
goals: t(lang, "moment.goals"),
|
|
746
|
+
scoreUpdated: t(lang, "moment.scoreUpdated"),
|
|
747
|
+
penalties: t(lang, "moment.penalties"),
|
|
748
|
+
fullTime: t(lang, "moment.fullTime"),
|
|
749
|
+
win: t(lang, "moment.win"),
|
|
750
|
+
draw: t(lang, "moment.draw")
|
|
751
|
+
};
|
|
752
|
+
}
|
|
660
753
|
var STAGE_KEYS = Object.freeze({
|
|
661
754
|
GROUP: "stage.groupStage",
|
|
662
755
|
R32: "stage.r32",
|
|
@@ -762,7 +855,7 @@ function formatTime(iso, opts = {}) {
|
|
|
762
855
|
}).format(when);
|
|
763
856
|
}
|
|
764
857
|
var COUNTDOWN_NOW = "now";
|
|
765
|
-
function countdown(iso, from
|
|
858
|
+
function countdown(iso, from) {
|
|
766
859
|
const when = parsedDate(iso);
|
|
767
860
|
if (!when) return UNKNOWN_TIME;
|
|
768
861
|
const ms = when.getTime() - from.getTime();
|
|
@@ -775,7 +868,7 @@ function countdown(iso, from = /* @__PURE__ */ new Date()) {
|
|
|
775
868
|
if (hours > 0) return `${hours}h${mins}m`;
|
|
776
869
|
return `${mins}m`;
|
|
777
870
|
}
|
|
778
|
-
function countdownPhrase(lang, iso, from
|
|
871
|
+
function countdownPhrase(lang, iso, from) {
|
|
779
872
|
const left = countdown(iso, from);
|
|
780
873
|
return left === COUNTDOWN_NOW ? t(lang, "countdown.now") : t(lang, "next.in", { countdown: left });
|
|
781
874
|
}
|
|
@@ -838,6 +931,65 @@ function withFlag(text, flag2, side) {
|
|
|
838
931
|
if (typeof flag2 !== "string" || flag2 === "") return text;
|
|
839
932
|
return side === "home" ? `${flag2} ${text}` : `${text} ${flag2}`;
|
|
840
933
|
}
|
|
934
|
+
var KIND_KEYS = Object.freeze({
|
|
935
|
+
GOAL: "event.goal",
|
|
936
|
+
OWN_GOAL: "event.ownGoal",
|
|
937
|
+
PEN: "event.penalty",
|
|
938
|
+
YELLOW: "event.yellow",
|
|
939
|
+
RED: "event.red",
|
|
940
|
+
SUB: "event.sub"
|
|
941
|
+
});
|
|
942
|
+
var KIND_MARKS = Object.freeze({
|
|
943
|
+
GOAL: "\u26BD",
|
|
944
|
+
OWN_GOAL: "\u26BD",
|
|
945
|
+
PEN: "\u26BD",
|
|
946
|
+
YELLOW: "\u{1F7E8}",
|
|
947
|
+
RED: "\u{1F7E5}",
|
|
948
|
+
SUB: "\u{1F504}"
|
|
949
|
+
});
|
|
950
|
+
function timelineParts(match, opts) {
|
|
951
|
+
const events = match.events ?? [];
|
|
952
|
+
if (events.length === 0) return [];
|
|
953
|
+
const cells = events.map((e) => {
|
|
954
|
+
const team = e.side === "home" ? match.home : match.away;
|
|
955
|
+
return {
|
|
956
|
+
minute: e.added !== void 0 ? `${e.minute}+${e.added}'` : `${e.minute}'`,
|
|
957
|
+
mark: KIND_MARKS[e.type],
|
|
958
|
+
kind: t(opts.lang, KIND_KEYS[e.type]),
|
|
959
|
+
label: withFlag(team.code, opts.flags ? team.flag : void 0, e.side),
|
|
960
|
+
player: e.player
|
|
961
|
+
};
|
|
962
|
+
});
|
|
963
|
+
const widest = (pick2) => Math.max(...cells.map((c) => displayWidth(pick2(c))));
|
|
964
|
+
const minuteW = widest((c) => c.minute);
|
|
965
|
+
const kindW = widest((c) => c.kind);
|
|
966
|
+
const labelW = widest((c) => c.label);
|
|
967
|
+
return cells.map((c) => {
|
|
968
|
+
const columns = ` ${padVisible(c.minute, minuteW)} ${c.mark} ${padVisible(c.kind, kindW)} ${padVisible(c.label, labelW)}`;
|
|
969
|
+
if (!c.player) return [{ text: columns.trimEnd() }];
|
|
970
|
+
return [{ text: `${columns} ` }, { text: c.player, style: "strong" }];
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
var SCORING = /* @__PURE__ */ new Set(["GOAL", "OWN_GOAL", "PEN"]);
|
|
974
|
+
function provenLastGoal(match) {
|
|
975
|
+
const score = match.score;
|
|
976
|
+
if (!score) return null;
|
|
977
|
+
let home = 0;
|
|
978
|
+
let away = 0;
|
|
979
|
+
let last = null;
|
|
980
|
+
const identities = /* @__PURE__ */ new Set();
|
|
981
|
+
for (const e of match.events ?? []) {
|
|
982
|
+
if (!SCORING.has(e.type)) continue;
|
|
983
|
+
const identity = JSON.stringify([e.type, e.minute, e.added ?? 0, e.side, e.player ?? null]);
|
|
984
|
+
if (identities.has(identity)) return null;
|
|
985
|
+
identities.add(identity);
|
|
986
|
+
if (e.side === "home") home++;
|
|
987
|
+
else if (e.side === "away") away++;
|
|
988
|
+
else continue;
|
|
989
|
+
last = e;
|
|
990
|
+
}
|
|
991
|
+
return last !== null && home === score.home && away === score.away ? last : null;
|
|
992
|
+
}
|
|
841
993
|
function outcomeFromScore(home, away) {
|
|
842
994
|
if (home > away) return "H";
|
|
843
995
|
if (home < away) return "A";
|
|
@@ -1109,18 +1261,33 @@ function sealScorePair(raw) {
|
|
|
1109
1261
|
const away = count(v.away, MAX_GOALS);
|
|
1110
1262
|
return home !== void 0 && away !== void 0 ? { home, away } : void 0;
|
|
1111
1263
|
}
|
|
1264
|
+
var MAX_ADDED_MINUTES = 30;
|
|
1265
|
+
var SIDES = /* @__PURE__ */ new Set(["home", "away"]);
|
|
1112
1266
|
function sealEvent(raw) {
|
|
1113
|
-
if (!raw || typeof raw !== "object") return
|
|
1267
|
+
if (!raw || typeof raw !== "object") return null;
|
|
1114
1268
|
const e = raw;
|
|
1115
1269
|
const type = member(e.type, EVENT_TYPES);
|
|
1116
|
-
if (!type) return
|
|
1270
|
+
if (!type) return null;
|
|
1117
1271
|
const minute = count(e.minute, MAX_MINUTE);
|
|
1118
|
-
if (minute === void 0) return
|
|
1119
|
-
|
|
1272
|
+
if (minute === void 0) return null;
|
|
1273
|
+
let added;
|
|
1274
|
+
if (e.added !== void 0) {
|
|
1275
|
+
added = count(e.added, MAX_ADDED_MINUTES);
|
|
1276
|
+
if (added === void 0 || added < 1) return null;
|
|
1277
|
+
}
|
|
1278
|
+
const side = member(e.side, SIDES);
|
|
1279
|
+
if (!side) return null;
|
|
1280
|
+
const out2 = { type, minute };
|
|
1281
|
+
if (added !== void 0) out2.added = added;
|
|
1282
|
+
out2.side = side;
|
|
1283
|
+
out2.teamCode = humanLabel(e.teamCode, TEAM_CODE_COLUMNS);
|
|
1120
1284
|
const player = humanLabel(e.player);
|
|
1121
1285
|
if (player) out2.player = player;
|
|
1122
1286
|
return out2;
|
|
1123
1287
|
}
|
|
1288
|
+
function byClock(a, b) {
|
|
1289
|
+
return a.minute - b.minute || (a.added ?? 0) - (b.added ?? 0);
|
|
1290
|
+
}
|
|
1124
1291
|
function sealMatch(parts, opts = {}) {
|
|
1125
1292
|
if (!parts || typeof parts !== "object") return malformed("match is not an object");
|
|
1126
1293
|
const id = opaqueId(parts.id, ESPN_ID);
|
|
@@ -1165,7 +1332,8 @@ function sealMatch(parts, opts = {}) {
|
|
|
1165
1332
|
winnerCode = claimedWinner;
|
|
1166
1333
|
}
|
|
1167
1334
|
}
|
|
1168
|
-
const
|
|
1335
|
+
const timelined = status === "LIVE" || status === "HT" || status === "FT";
|
|
1336
|
+
const events = opts.events === false || !timelined ? [] : takeBounded(parts.events, MAX_MATCH_EVENTS).map(sealEvent).filter((e) => !!e).sort(byClock);
|
|
1169
1337
|
const out2 = { id, stage };
|
|
1170
1338
|
if (stageLabel2) out2.stageLabel = stageLabel2;
|
|
1171
1339
|
if (group) out2.group = group;
|
|
@@ -4307,6 +4475,75 @@ function parseMinute(st) {
|
|
|
4307
4475
|
}
|
|
4308
4476
|
return void 0;
|
|
4309
4477
|
}
|
|
4478
|
+
var DETAIL_FLAGS = ["scoringPlay", "yellowCard", "redCard", "penaltyKick", "ownGoal", "shootout"];
|
|
4479
|
+
var DETAIL_MINUTE = /^(\d{1,3})'$/;
|
|
4480
|
+
var DETAIL_MINUTE_ADDED = /^(\d{1,3})'\+(\d{1,2})'$/;
|
|
4481
|
+
function detailKind(d) {
|
|
4482
|
+
const f = {};
|
|
4483
|
+
for (const name of DETAIL_FLAGS) {
|
|
4484
|
+
const v = flag(d[name]);
|
|
4485
|
+
if (v === void 0) return void 0;
|
|
4486
|
+
f[name] = v;
|
|
4487
|
+
}
|
|
4488
|
+
if (f.shootout) return void 0;
|
|
4489
|
+
const { scoringPlay: s, yellowCard: y, redCard: r, penaltyKick: p, ownGoal: o } = f;
|
|
4490
|
+
if (s && !y && !r) {
|
|
4491
|
+
if (!p && !o) return "GOAL";
|
|
4492
|
+
if (o && !p) return "OWN_GOAL";
|
|
4493
|
+
if (p && !o) return "PEN";
|
|
4494
|
+
return void 0;
|
|
4495
|
+
}
|
|
4496
|
+
if (p || o || s) return void 0;
|
|
4497
|
+
if (y && !r) return "YELLOW";
|
|
4498
|
+
if (r && !y) return "RED";
|
|
4499
|
+
return void 0;
|
|
4500
|
+
}
|
|
4501
|
+
function detailMinute(clock) {
|
|
4502
|
+
if (!clock || typeof clock !== "object") return void 0;
|
|
4503
|
+
const dv = clock.displayValue;
|
|
4504
|
+
if (typeof dv !== "string") return void 0;
|
|
4505
|
+
const plain2 = DETAIL_MINUTE.exec(dv);
|
|
4506
|
+
if (plain2) return { minute: Number(plain2[1]) };
|
|
4507
|
+
const extra = DETAIL_MINUTE_ADDED.exec(dv);
|
|
4508
|
+
if (!extra) return void 0;
|
|
4509
|
+
return { minute: Number(extra[1]), added: Number(extra[2]) };
|
|
4510
|
+
}
|
|
4511
|
+
function rawTeamId(holder) {
|
|
4512
|
+
if (!holder || typeof holder !== "object") return void 0;
|
|
4513
|
+
const team = holder.team;
|
|
4514
|
+
if (!team || typeof team !== "object") return void 0;
|
|
4515
|
+
return opaqueId(team.id, ESPN_ID);
|
|
4516
|
+
}
|
|
4517
|
+
function detailEvents(details, sides) {
|
|
4518
|
+
const out2 = [];
|
|
4519
|
+
for (const raw of takeBounded(details, MAX_MATCH_EVENTS)) {
|
|
4520
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
4521
|
+
const d = raw;
|
|
4522
|
+
const type = detailKind(d);
|
|
4523
|
+
if (!type) continue;
|
|
4524
|
+
const when = detailMinute(d.clock);
|
|
4525
|
+
if (!when) continue;
|
|
4526
|
+
const teamId = rawTeamId(d);
|
|
4527
|
+
const side = teamId === void 0 ? void 0 : teamId === sides.homeId ? "home" : teamId === sides.awayId ? "away" : void 0;
|
|
4528
|
+
if (!side) continue;
|
|
4529
|
+
const event = { type, minute: when.minute };
|
|
4530
|
+
if (when.added !== void 0) event.added = when.added;
|
|
4531
|
+
event.side = side;
|
|
4532
|
+
event.teamCode = side === "home" ? sides.homeCode : sides.awayCode;
|
|
4533
|
+
const player = detailPlayer(d.athletesInvolved, type === "OWN_GOAL" ? side === "home" ? sides.awayId : sides.homeId : teamId);
|
|
4534
|
+
if (player) event.player = player;
|
|
4535
|
+
out2.push(event);
|
|
4536
|
+
}
|
|
4537
|
+
return out2;
|
|
4538
|
+
}
|
|
4539
|
+
function detailPlayer(athletes, expectedTeamId) {
|
|
4540
|
+
if (!Array.isArray(athletes) || athletes.length !== 1 || expectedTeamId === void 0) return void 0;
|
|
4541
|
+
const a = athletes[0];
|
|
4542
|
+
if (rawTeamId(a) !== expectedTeamId) return void 0;
|
|
4543
|
+
const name = a.displayName;
|
|
4544
|
+
if (!isHumanLabel(name)) return void 0;
|
|
4545
|
+
return humanLabel(name) || void 0;
|
|
4546
|
+
}
|
|
4310
4547
|
var MAX_SLUG_UNITS2 = 64;
|
|
4311
4548
|
var SLUG = /^[a-z0-9-]+$/;
|
|
4312
4549
|
var EXACT_STAGE = Object.freeze({
|
|
@@ -4428,7 +4665,14 @@ function parseEspnEvent(raw, ctx = {}) {
|
|
|
4428
4665
|
status,
|
|
4429
4666
|
winnerCode,
|
|
4430
4667
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4431
|
-
|
|
4668
|
+
// The goals and cards: ESPN's `details`, read by the detail grammar; the
|
|
4669
|
+
// seal keeps them only on a status that holds a score.
|
|
4670
|
+
events: detailEvents(comp?.details, {
|
|
4671
|
+
homeId: rawTeamId(homeRaw),
|
|
4672
|
+
awayId: rawTeamId(awayRaw),
|
|
4673
|
+
homeCode: home.code,
|
|
4674
|
+
awayCode: away.code
|
|
4675
|
+
})
|
|
4432
4676
|
},
|
|
4433
4677
|
// The final reseal states the same kind the competitors were built with.
|
|
4434
4678
|
{ teamKind: kind }
|
|
@@ -4745,7 +4989,7 @@ function fetchMeta(result) {
|
|
|
4745
4989
|
}
|
|
4746
4990
|
var ESPN_SOCCER = "https://site.api.espn.com/apis/site/v2/sports/soccer";
|
|
4747
4991
|
var DEFAULT_COMPETITION = BUNDLED_SLUG;
|
|
4748
|
-
var USER_AGENT = `claudinho/${"0.
|
|
4992
|
+
var USER_AGENT = `claudinho/${"0.12.0"} (+https://github.com/arturogarrido/claudinho)`;
|
|
4749
4993
|
var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
4750
4994
|
var DEFAULT_COOLDOWN_MS = 5 * 6e4;
|
|
4751
4995
|
var MAX_COOLDOWN_MS = 15 * 6e4;
|
|
@@ -6237,6 +6481,9 @@ function mergeLive(base, live) {
|
|
|
6237
6481
|
function clockOf(adapter) {
|
|
6238
6482
|
return new Date(adapter.now?.() ?? Date.now());
|
|
6239
6483
|
}
|
|
6484
|
+
function requestClock(adapter, given) {
|
|
6485
|
+
return given ?? clockOf(adapter);
|
|
6486
|
+
}
|
|
6240
6487
|
function providerDayOf(adapter, instant) {
|
|
6241
6488
|
if (Number.isNaN(instant.getTime())) return "";
|
|
6242
6489
|
return adapter.bucketDay?.(instant) || instant.toISOString().slice(0, 10);
|
|
@@ -6411,7 +6658,7 @@ async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Dat
|
|
|
6411
6658
|
});
|
|
6412
6659
|
let refreshRead = {};
|
|
6413
6660
|
if (candidate) {
|
|
6414
|
-
const { partial, served: _served, ...r } = await getMatchById(adapter, candidate.id);
|
|
6661
|
+
const { partial, served: _served, ...r } = await getMatchById(adapter, candidate.id, now);
|
|
6415
6662
|
refreshRead = partial ? { partial } : {};
|
|
6416
6663
|
const m = r.degraded ? candidate : r.match ?? candidate;
|
|
6417
6664
|
if (!isFinished(m.status)) return { ...r, match: m, ...bothReads(windowRead, refreshRead) };
|
|
@@ -6644,10 +6891,19 @@ async function getScheduleAhead(adapter, now = /* @__PURE__ */ new Date()) {
|
|
|
6644
6891
|
};
|
|
6645
6892
|
}
|
|
6646
6893
|
async function getLiveMatches(adapter, now = /* @__PURE__ */ new Date()) {
|
|
6647
|
-
const { complete: _complete, ...result } = await getLiveRead(adapter, now);
|
|
6894
|
+
const { complete: _complete, finished: _finished, finishedTruncated: _truncated, ...result } = await getLiveRead(adapter, now);
|
|
6648
6895
|
return result;
|
|
6649
6896
|
}
|
|
6650
|
-
|
|
6897
|
+
var MAX_LIVE_FINISHED = 64;
|
|
6898
|
+
function finishedOf(records, seen) {
|
|
6899
|
+
const wanted = new Set(takeBounded(seen, MAX_LIVE_FINISHED));
|
|
6900
|
+
const done = records.filter((m) => m.status === "FT" && m.score !== void 0);
|
|
6901
|
+
const first = done.filter((m) => wanted.has(m.id));
|
|
6902
|
+
const rest = done.filter((m) => !wanted.has(m.id));
|
|
6903
|
+
const all = [...first, ...rest];
|
|
6904
|
+
return { finished: all.slice(0, MAX_LIVE_FINISHED), truncated: all.length > MAX_LIVE_FINISHED };
|
|
6905
|
+
}
|
|
6906
|
+
async function getLiveRead(adapter, now = /* @__PURE__ */ new Date(), seen = []) {
|
|
6651
6907
|
try {
|
|
6652
6908
|
const day = now.toISOString().slice(0, 10);
|
|
6653
6909
|
const fetched = adapter.fetchWindow ? await adapter.fetchWindow(shiftUtcDate(day, -1), shiftUtcDate(day, 1), { acrossSeasons: true }) : await adapter.fetchLive();
|
|
@@ -6655,6 +6911,7 @@ async function getLiveRead(adapter, now = /* @__PURE__ */ new Date()) {
|
|
|
6655
6911
|
const season = meta?.season;
|
|
6656
6912
|
const matches = fetched.filter((m) => isLive(m.status));
|
|
6657
6913
|
const between = betweenEditionsOf(adapter, meta, fetched, providerDayOf(adapter, now));
|
|
6914
|
+
const done = finishedOf(fetched, seen);
|
|
6658
6915
|
return {
|
|
6659
6916
|
matches,
|
|
6660
6917
|
degraded: false,
|
|
@@ -6666,10 +6923,12 @@ async function getLiveRead(adapter, now = /* @__PURE__ */ new Date()) {
|
|
|
6666
6923
|
// boolean below is the refresher's, unchanged: false for that, for an
|
|
6667
6924
|
// adapter that said nothing, and for a failed read alike.
|
|
6668
6925
|
...partialOfRead(meta),
|
|
6669
|
-
complete: meta?.complete === true
|
|
6926
|
+
complete: meta?.complete === true,
|
|
6927
|
+
finished: done.finished,
|
|
6928
|
+
finishedTruncated: done.truncated
|
|
6670
6929
|
};
|
|
6671
6930
|
} catch {
|
|
6672
|
-
return { matches: [], degraded: true, complete: false };
|
|
6931
|
+
return { matches: [], degraded: true, complete: false, finished: [], finishedTruncated: false };
|
|
6673
6932
|
}
|
|
6674
6933
|
}
|
|
6675
6934
|
function norm2(s) {
|
|
@@ -6905,22 +7164,29 @@ function sealMarketSignal(raw, options = {}) {
|
|
|
6905
7164
|
if (sourceMarketId) out2.sourceMarketId = sourceMarketId;
|
|
6906
7165
|
out2.asOf = canonicalTimestamp(s.asOf) ?? "";
|
|
6907
7166
|
out2.fetchedAt = canonicalTimestamp(s.fetchedAt) ?? "";
|
|
7167
|
+
const judgedAt = options.now ?? /* @__PURE__ */ new Date();
|
|
7168
|
+
const fetchedMs = Date.parse(out2.fetchedAt);
|
|
7169
|
+
if (out2.fetchedAt === "" || !Number.isFinite(fetchedMs)) {
|
|
7170
|
+
return malformed("signal carries no readable fetch time");
|
|
7171
|
+
}
|
|
7172
|
+
if (fetchedMs > judgedAt.getTime()) {
|
|
7173
|
+
return unresolved("signal was fetched after the clock it is judged by");
|
|
7174
|
+
}
|
|
6908
7175
|
out2.outcomes = outcomes;
|
|
6909
7176
|
const isAmbiguous = s.ambiguous !== false;
|
|
6910
7177
|
const favorite = isAmbiguous ? void 0 : deriveFavorite(outcomes);
|
|
6911
7178
|
if (favorite) out2.favorite = favorite;
|
|
6912
7179
|
if (liquidity !== void 0) out2.liquidity = liquidity;
|
|
6913
7180
|
if (volume24h !== void 0) out2.volume24h = volume24h;
|
|
6914
|
-
out2.stale =
|
|
7181
|
+
out2.stale = isStaleSignal(out2, { now: judgedAt, maxAgeMs: options.maxAgeMs });
|
|
6915
7182
|
out2.ambiguous = isAmbiguous || out2.source === "";
|
|
6916
|
-
out2.stale = out2.stale || isStaleSignal(out2, { now: options.now, maxAgeMs: options.maxAgeMs });
|
|
6917
7183
|
return valid(out2);
|
|
6918
7184
|
}
|
|
6919
7185
|
function parseCachedMarketSignal(raw, options = {}) {
|
|
6920
7186
|
return sealMarketSignal(raw, options);
|
|
6921
7187
|
}
|
|
6922
7188
|
var DEFAULT_MAX_AGE_MS = 15 * 6e4;
|
|
6923
|
-
function marketRelevant(match, now
|
|
7189
|
+
function marketRelevant(match, now) {
|
|
6924
7190
|
if (isLive(match.status)) return true;
|
|
6925
7191
|
if (isFinished(match.status)) return false;
|
|
6926
7192
|
const k = Date.parse(match.kickoff);
|
|
@@ -7009,12 +7275,13 @@ function buildMarketSignal(input) {
|
|
|
7009
7275
|
const outcomes = normalizeOutcomes(input.outcomes);
|
|
7010
7276
|
const ambiguous2 = input.ambiguous === true || !mapsCleanly(input.match, outcomes);
|
|
7011
7277
|
const favorite = ambiguous2 ? void 0 : deriveFavorite(outcomes);
|
|
7278
|
+
const clock = input.now ?? /* @__PURE__ */ new Date();
|
|
7012
7279
|
const signal = {
|
|
7013
7280
|
matchId: input.match.id,
|
|
7014
7281
|
source: input.source,
|
|
7015
7282
|
sourceMarketId: input.sourceMarketId,
|
|
7016
7283
|
asOf: input.asOf,
|
|
7017
|
-
fetchedAt: input.fetchedAt ??
|
|
7284
|
+
fetchedAt: input.fetchedAt ?? clock.toISOString(),
|
|
7018
7285
|
outcomes,
|
|
7019
7286
|
favorite,
|
|
7020
7287
|
liquidity: input.liquidity,
|
|
@@ -7022,8 +7289,8 @@ function buildMarketSignal(input) {
|
|
|
7022
7289
|
stale: false,
|
|
7023
7290
|
ambiguous: ambiguous2
|
|
7024
7291
|
};
|
|
7025
|
-
signal.stale = isStaleSignal(signal, { now:
|
|
7026
|
-
const sealed = sealMarketSignal(signal, { now:
|
|
7292
|
+
signal.stale = isStaleSignal(signal, { now: clock, maxAgeMs: input.maxAgeMs });
|
|
7293
|
+
const sealed = sealMarketSignal(signal, { now: clock, maxAgeMs: input.maxAgeMs });
|
|
7027
7294
|
if (sealed.kind !== "valid") {
|
|
7028
7295
|
return { ...signal, outcomes: [], favorite: void 0, stale: true, ambiguous: true };
|
|
7029
7296
|
}
|
|
@@ -8730,6 +8997,14 @@ function makeT(lang) {
|
|
|
8730
8997
|
};
|
|
8731
8998
|
}
|
|
8732
8999
|
|
|
9000
|
+
// src/options.ts
|
|
9001
|
+
import { InvalidArgumentError } from "commander";
|
|
9002
|
+
function positiveInteger(value) {
|
|
9003
|
+
const n = /^\d+$/.test(value) ? Number.parseInt(value, 10) : 0;
|
|
9004
|
+
if (!(n > 0)) throw new InvalidArgumentError("a positive integer");
|
|
9005
|
+
return n;
|
|
9006
|
+
}
|
|
9007
|
+
|
|
8733
9008
|
// src/tagline.ts
|
|
8734
9009
|
var NAMED = ["the World Cup", "the Premier League", "LALIGA", "the Champions League"];
|
|
8735
9010
|
var TAGLINE = `Live football scores, fixtures and standings in your terminal and your Claude Code and Cursor CLI statusline, for the competition you follow: ${NAMED.join(", ")} and ${SUPPORTED.length - NAMED.length} more.`;
|
|
@@ -8769,17 +9044,33 @@ function painterFor(cfg) {
|
|
|
8769
9044
|
return paint(cfg.color);
|
|
8770
9045
|
}
|
|
8771
9046
|
function statusToken(m, t2, c) {
|
|
9047
|
+
const word = statusWord(m, t2);
|
|
9048
|
+
switch (m.status) {
|
|
9049
|
+
case "LIVE":
|
|
9050
|
+
return c.green(word);
|
|
9051
|
+
case "HT":
|
|
9052
|
+
return c.yellow(word);
|
|
9053
|
+
case "FT":
|
|
9054
|
+
return c.gray(word);
|
|
9055
|
+
case "POSTPONED":
|
|
9056
|
+
case "CANCELLED":
|
|
9057
|
+
return c.red(word);
|
|
9058
|
+
default:
|
|
9059
|
+
return "";
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9062
|
+
function statusWord(m, t2) {
|
|
8772
9063
|
switch (m.status) {
|
|
8773
9064
|
case "LIVE":
|
|
8774
|
-
return
|
|
9065
|
+
return m.minute ? `${m.minute}'` : t2("status.live");
|
|
8775
9066
|
case "HT":
|
|
8776
|
-
return
|
|
9067
|
+
return t2("status.ht");
|
|
8777
9068
|
case "FT":
|
|
8778
|
-
return
|
|
9069
|
+
return t2("status.ft");
|
|
8779
9070
|
case "POSTPONED":
|
|
8780
|
-
return
|
|
9071
|
+
return t2("status.postponed");
|
|
8781
9072
|
case "CANCELLED":
|
|
8782
|
-
return
|
|
9073
|
+
return t2("status.cancelled");
|
|
8783
9074
|
default:
|
|
8784
9075
|
return "";
|
|
8785
9076
|
}
|
|
@@ -8969,7 +9260,9 @@ function readState(source, competition) {
|
|
|
8969
9260
|
const bytes = readSmallFile(cachePath(source, competition), MAX_STATE_BYTES);
|
|
8970
9261
|
if (!bytes) return void 0;
|
|
8971
9262
|
const parsed = JSON.parse(bytes.toString("utf8"));
|
|
8972
|
-
|
|
9263
|
+
if (!isCacheState(parsed)) return void 0;
|
|
9264
|
+
if (parsed.liveComplete !== void 0 && typeof parsed.liveComplete !== "boolean") delete parsed.liveComplete;
|
|
9265
|
+
return parsed;
|
|
8973
9266
|
} catch {
|
|
8974
9267
|
return void 0;
|
|
8975
9268
|
}
|
|
@@ -9000,12 +9293,60 @@ function writeState(state, now = stampOrNow(state.updatedAt)) {
|
|
|
9000
9293
|
const until = envelopeDeadline(replaced, now);
|
|
9001
9294
|
if (until !== void 0 && !writeBackoffNote(state.source, state.competition, until, now)) return false;
|
|
9002
9295
|
}
|
|
9003
|
-
writeFileAtomic(
|
|
9004
|
-
cachePath(state.source, state.competition),
|
|
9005
|
-
JSON.stringify({ ...state, version: CACHE_VERSION })
|
|
9006
|
-
);
|
|
9296
|
+
writeFileAtomic(cachePath(state.source, state.competition), boundedSnapshot(withoutEvents(state)));
|
|
9007
9297
|
return true;
|
|
9008
9298
|
}
|
|
9299
|
+
function boundedSnapshot(state) {
|
|
9300
|
+
const serialize = (s) => JSON.stringify({ ...s, version: CACHE_VERSION });
|
|
9301
|
+
let text = serialize(state);
|
|
9302
|
+
if (Buffer.byteLength(text, "utf8") <= MAX_STATE_BYTES) return text;
|
|
9303
|
+
let next = state;
|
|
9304
|
+
const play = state.play;
|
|
9305
|
+
if (play && typeof play === "object" && !Array.isArray(play) && Array.isArray(play.finished)) {
|
|
9306
|
+
next = { ...next, play: { ...play, finished: [] } };
|
|
9307
|
+
text = serialize(next);
|
|
9308
|
+
if (Buffer.byteLength(text, "utf8") <= MAX_STATE_BYTES) return text;
|
|
9309
|
+
}
|
|
9310
|
+
if (Array.isArray(next.live)) {
|
|
9311
|
+
next = { ...next, live: next.live.map(dropLastGoal) };
|
|
9312
|
+
text = serialize(next);
|
|
9313
|
+
if (Buffer.byteLength(text, "utf8") <= MAX_STATE_BYTES) return text;
|
|
9314
|
+
}
|
|
9315
|
+
if ("play" in next) {
|
|
9316
|
+
const { play: _play, ...withoutPlay } = next;
|
|
9317
|
+
text = serialize(withoutPlay);
|
|
9318
|
+
}
|
|
9319
|
+
return text;
|
|
9320
|
+
}
|
|
9321
|
+
function dropLastGoal(m) {
|
|
9322
|
+
if (!m || typeof m !== "object" || !("lastGoal" in m)) return m;
|
|
9323
|
+
const copy = { ...m };
|
|
9324
|
+
delete copy.lastGoal;
|
|
9325
|
+
return copy;
|
|
9326
|
+
}
|
|
9327
|
+
function withoutEvents(state) {
|
|
9328
|
+
const strip = (list) => Array.isArray(list) ? list.map(dropEvents) : list;
|
|
9329
|
+
const out2 = { ...state, live: strip(state.live) };
|
|
9330
|
+
if (state.fixtures !== void 0) out2.fixtures = strip(state.fixtures);
|
|
9331
|
+
if (state.schedule?.fixtures !== void 0) out2.schedule = { ...state.schedule, fixtures: strip(state.schedule.fixtures) };
|
|
9332
|
+
const play = state.play;
|
|
9333
|
+
if (play && typeof play === "object" && !Array.isArray(play) && Array.isArray(play.finished)) {
|
|
9334
|
+
const p = play;
|
|
9335
|
+
out2.play = {
|
|
9336
|
+
...p,
|
|
9337
|
+
finished: p.finished.map(
|
|
9338
|
+
(e) => e && typeof e === "object" && !Array.isArray(e) ? { ...e, match: dropEvents(e.match) } : e
|
|
9339
|
+
)
|
|
9340
|
+
};
|
|
9341
|
+
}
|
|
9342
|
+
return out2;
|
|
9343
|
+
}
|
|
9344
|
+
function dropEvents(m) {
|
|
9345
|
+
if (!m || typeof m !== "object" || !("events" in m)) return m;
|
|
9346
|
+
const copy = { ...m };
|
|
9347
|
+
delete copy.events;
|
|
9348
|
+
return copy;
|
|
9349
|
+
}
|
|
9009
9350
|
function stampOrNow(stamp) {
|
|
9010
9351
|
return validStamp(stamp) ? Date.parse(stamp) : Date.now();
|
|
9011
9352
|
}
|
|
@@ -9229,7 +9570,7 @@ function isUsableSignal(s) {
|
|
|
9229
9570
|
if (!s.favorite) return false;
|
|
9230
9571
|
return hasSaneDistribution(s.outcomes);
|
|
9231
9572
|
}
|
|
9232
|
-
function readMarketCache(source, competition, now = Date.now()) {
|
|
9573
|
+
function readMarketCache(source, competition, now = Date.now(), requestNow) {
|
|
9233
9574
|
const signals = /* @__PURE__ */ new Map();
|
|
9234
9575
|
const checked = /* @__PURE__ */ new Set();
|
|
9235
9576
|
const file = readFile();
|
|
@@ -9256,7 +9597,7 @@ function readMarketCache(source, competition, now = Date.now()) {
|
|
|
9256
9597
|
checked.add(id);
|
|
9257
9598
|
continue;
|
|
9258
9599
|
}
|
|
9259
|
-
const sealed = parseCachedMarketSignal(entry.signal, { now:
|
|
9600
|
+
const sealed = parseCachedMarketSignal(entry.signal, { now: requestNow });
|
|
9260
9601
|
if (sealed.kind !== "valid") continue;
|
|
9261
9602
|
const clean = sealed.value;
|
|
9262
9603
|
if (clean.matchId !== id || clean.source !== source) continue;
|
|
@@ -9354,6 +9695,104 @@ function copyToClipboard(text, platform = process.platform) {
|
|
|
9354
9695
|
return false;
|
|
9355
9696
|
}
|
|
9356
9697
|
|
|
9698
|
+
// src/play.ts
|
|
9699
|
+
var FINISHED_TTL_MS = 10 * 6e4;
|
|
9700
|
+
var SEEN_TTL_MS = 6 * 60 * 6e4;
|
|
9701
|
+
var MAX_PLAY = 64;
|
|
9702
|
+
var NO_READ = Object.freeze({ whole: false, matches: [], finished: [], finishedTruncated: false });
|
|
9703
|
+
var ID = /^[A-Za-z0-9._:-]{1,64}$/;
|
|
9704
|
+
function believedAt(at, now, ttl) {
|
|
9705
|
+
if (!validStamp(at)) return false;
|
|
9706
|
+
const ms = Date.parse(at);
|
|
9707
|
+
return ms <= now && now - ms < ttl;
|
|
9708
|
+
}
|
|
9709
|
+
function playRecordIds(play, now) {
|
|
9710
|
+
const ids = /* @__PURE__ */ new Set();
|
|
9711
|
+
if (!play || typeof play !== "object" || Array.isArray(play)) return ids;
|
|
9712
|
+
const p = play;
|
|
9713
|
+
for (const raw of takeBounded(p.seen, MAX_PLAY)) {
|
|
9714
|
+
if (!raw || typeof raw !== "object") continue;
|
|
9715
|
+
const { id, at } = raw;
|
|
9716
|
+
if (typeof id === "string" && ID.test(id) && believedAt(at, now, SEEN_TTL_MS)) ids.add(id);
|
|
9717
|
+
}
|
|
9718
|
+
for (const raw of takeBounded(p.finished, MAX_PLAY)) {
|
|
9719
|
+
if (!raw || typeof raw !== "object") continue;
|
|
9720
|
+
const { match, at } = raw;
|
|
9721
|
+
if (!match || typeof match !== "object" || !believedAt(at, now, FINISHED_TTL_MS)) continue;
|
|
9722
|
+
const id = match.id;
|
|
9723
|
+
if (typeof id === "string" && ID.test(id)) ids.add(id);
|
|
9724
|
+
}
|
|
9725
|
+
return ids;
|
|
9726
|
+
}
|
|
9727
|
+
function believedPlay(prev, now, kind) {
|
|
9728
|
+
const out2 = { seen: [], finished: [] };
|
|
9729
|
+
if (!prev || typeof prev !== "object" || Array.isArray(prev)) return out2;
|
|
9730
|
+
const p = prev;
|
|
9731
|
+
const ids = /* @__PURE__ */ new Set();
|
|
9732
|
+
for (const raw of takeBounded(p.seen, MAX_PLAY)) {
|
|
9733
|
+
if (!raw || typeof raw !== "object") continue;
|
|
9734
|
+
const { id, at } = raw;
|
|
9735
|
+
if (typeof id !== "string" || !ID.test(id) || ids.has(id) || !believedAt(at, now, SEEN_TTL_MS)) continue;
|
|
9736
|
+
ids.add(id);
|
|
9737
|
+
out2.seen.push({ id, at });
|
|
9738
|
+
}
|
|
9739
|
+
const done = /* @__PURE__ */ new Set();
|
|
9740
|
+
for (const raw of takeBounded(p.finished, MAX_PLAY)) {
|
|
9741
|
+
if (!raw || typeof raw !== "object") continue;
|
|
9742
|
+
const { match, at } = raw;
|
|
9743
|
+
if (!believedAt(at, now, FINISHED_TTL_MS)) continue;
|
|
9744
|
+
const sealed = parsedValue(parseCachedMatch(match, { events: false, teamKind: kind }));
|
|
9745
|
+
if (sealed?.status !== "FT" || !sealed.score || done.has(sealed.id)) continue;
|
|
9746
|
+
done.add(sealed.id);
|
|
9747
|
+
out2.finished.push({ match: sealed, at });
|
|
9748
|
+
}
|
|
9749
|
+
return out2;
|
|
9750
|
+
}
|
|
9751
|
+
function advancePlay(prev, read, now, kind = "club") {
|
|
9752
|
+
const nowMs = now.getTime();
|
|
9753
|
+
const believed = believedPlay(prev, nowMs, kind);
|
|
9754
|
+
if (!read.whole) return believed;
|
|
9755
|
+
const at = now.toISOString();
|
|
9756
|
+
const inPlay = /* @__PURE__ */ new Set();
|
|
9757
|
+
const seenNow = [];
|
|
9758
|
+
for (const m of read.matches) {
|
|
9759
|
+
if (typeof m?.id !== "string" || inPlay.has(m.id)) continue;
|
|
9760
|
+
inPlay.add(m.id);
|
|
9761
|
+
seenNow.push({ id: m.id, at });
|
|
9762
|
+
}
|
|
9763
|
+
const finishedRead = /* @__PURE__ */ new Map();
|
|
9764
|
+
for (const m of read.finished) {
|
|
9765
|
+
if (typeof m?.id === "string" && !finishedRead.has(m.id)) finishedRead.set(m.id, m);
|
|
9766
|
+
}
|
|
9767
|
+
const finished = [];
|
|
9768
|
+
const held = /* @__PURE__ */ new Set();
|
|
9769
|
+
for (const e of believed.finished) {
|
|
9770
|
+
const id = e.match.id;
|
|
9771
|
+
if (inPlay.has(id)) continue;
|
|
9772
|
+
const again = finishedRead.get(id);
|
|
9773
|
+
if (again) finished.push({ match: again, at: e.at });
|
|
9774
|
+
else if (read.finishedTruncated) finished.push(e);
|
|
9775
|
+
else continue;
|
|
9776
|
+
held.add(id);
|
|
9777
|
+
}
|
|
9778
|
+
const keptSeen = [];
|
|
9779
|
+
for (const e of believed.seen) {
|
|
9780
|
+
if (inPlay.has(e.id) || held.has(e.id)) continue;
|
|
9781
|
+
const done = finishedRead.get(e.id);
|
|
9782
|
+
if (done) {
|
|
9783
|
+
finished.push({ match: done, at });
|
|
9784
|
+
held.add(e.id);
|
|
9785
|
+
} else if (read.finishedTruncated) {
|
|
9786
|
+
keptSeen.push(e);
|
|
9787
|
+
}
|
|
9788
|
+
}
|
|
9789
|
+
const seen = [...keptSeen, ...seenNow];
|
|
9790
|
+
return {
|
|
9791
|
+
seen: seen.length > MAX_PLAY ? seen.slice(seen.length - MAX_PLAY) : seen,
|
|
9792
|
+
finished: finished.length > MAX_PLAY ? finished.slice(finished.length - MAX_PLAY) : finished
|
|
9793
|
+
};
|
|
9794
|
+
}
|
|
9795
|
+
|
|
9357
9796
|
// src/scheduleSlice.ts
|
|
9358
9797
|
var SCHEDULE_DISPLAY_MAX = 64;
|
|
9359
9798
|
var RELEVANT_BACK_MS = 6 * 60 * 6e4;
|
|
@@ -9448,6 +9887,7 @@ function applyDiscovery(prev, answer, now) {
|
|
|
9448
9887
|
// src/statusline.ts
|
|
9449
9888
|
var DISPLAY_STALE_MS = 5 * 6e4;
|
|
9450
9889
|
var TOURNAMENT_COMPLETE_LINE = "\u26BD World Cup 2026 is complete \xB7 claudinho follow --list";
|
|
9890
|
+
var NOTHING_KNOWN_LINE = "\u26BD \u2014";
|
|
9451
9891
|
var FIRST_RUN_LINE = "\u26BD claudinho follow";
|
|
9452
9892
|
function pickAmbientMatch(matches, pick2) {
|
|
9453
9893
|
if (!pick2) return [...matches];
|
|
@@ -9482,8 +9922,17 @@ function isMatchShaped(m) {
|
|
|
9482
9922
|
const x = m;
|
|
9483
9923
|
return !!x && typeof x === "object" && typeof x.id === "string" && typeof x.kickoff === "string" && !!x.home?.code && !!x.away?.code;
|
|
9484
9924
|
}
|
|
9485
|
-
|
|
9486
|
-
|
|
9925
|
+
var KICKOFF_DUE_MS = 30 * 6e4;
|
|
9926
|
+
function isDue(m, nowMs, played) {
|
|
9927
|
+
if (m.status !== "SCHEDULED" || played.has(m.id)) return false;
|
|
9928
|
+
const k = Date.parse(m.kickoff);
|
|
9929
|
+
return k <= nowMs && nowMs <= k + KICKOFF_DUE_MS;
|
|
9930
|
+
}
|
|
9931
|
+
function isAhead(m, nowMs) {
|
|
9932
|
+
return Date.parse(m.kickoff) > nowMs && isUpcoming(m, new Date(nowMs));
|
|
9933
|
+
}
|
|
9934
|
+
function countdownCandidates(nowMs, fixtures = allFixtures(), played) {
|
|
9935
|
+
return [...fixtures].sort(byKickoff).filter((m) => isResolvedFixture(m) && (isAhead(m, nowMs) || isDue(m, nowMs, played)));
|
|
9487
9936
|
}
|
|
9488
9937
|
function defaultTeamKind(defaultCompetition) {
|
|
9489
9938
|
return defaultCompetition === false ? "club" : "nation";
|
|
@@ -9491,14 +9940,38 @@ function defaultTeamKind(defaultCompetition) {
|
|
|
9491
9940
|
function teamTok(t2, flags) {
|
|
9492
9941
|
return flags && t2.flag ? t2.flag : t2.code;
|
|
9493
9942
|
}
|
|
9494
|
-
function matchSegment(m, compact, flags) {
|
|
9495
|
-
const
|
|
9496
|
-
|
|
9497
|
-
|
|
9943
|
+
function matchSegment(m, compact, flags, lang) {
|
|
9944
|
+
const ht = lang === void 0 ? "HT" : t(lang, "status.ht");
|
|
9945
|
+
const live = lang === void 0 ? "LIVE" : t(lang, "status.live");
|
|
9946
|
+
const minute = m.status === "HT" ? ht : m.minute ? `${m.minute}'` : live;
|
|
9947
|
+
const home = !flags ? m.home.code : compact ? teamTok(m.home, true) : withFlag(m.home.code, m.home.flag, "home");
|
|
9948
|
+
const away = !flags ? m.away.code : compact ? teamTok(m.away, true) : withFlag(m.away.code, m.away.flag, "away");
|
|
9949
|
+
const score = scoreline(m);
|
|
9950
|
+
return { text: `${home} ${score} ${away} ${minute}`, score: [home.length + 1, home.length + 1 + score.length] };
|
|
9951
|
+
}
|
|
9952
|
+
var plain = (text) => ({ text, strong: [] });
|
|
9953
|
+
function sharedPrefix(a, b) {
|
|
9954
|
+
const n = Math.min(a.length, b.length);
|
|
9955
|
+
let i = 0;
|
|
9956
|
+
while (i < n && a.charCodeAt(i) === b.charCodeAt(i)) i++;
|
|
9957
|
+
return i;
|
|
9958
|
+
}
|
|
9959
|
+
function keptRanges(from, to, ranges) {
|
|
9960
|
+
if (from === to) return [...ranges];
|
|
9961
|
+
const kept = sharedPrefix(from, to);
|
|
9962
|
+
return ranges.filter(([, end]) => end <= kept);
|
|
9963
|
+
}
|
|
9964
|
+
function partsOf(line, strong) {
|
|
9965
|
+
const parts = [];
|
|
9966
|
+
let at = 0;
|
|
9967
|
+
for (const [start, end] of strong) {
|
|
9968
|
+
if (start < at || end <= start || end > line.length) return [{ text: line }];
|
|
9969
|
+
if (start > at) parts.push({ text: line.slice(at, start) });
|
|
9970
|
+
parts.push({ text: line.slice(start, end), style: "strong" });
|
|
9971
|
+
at = end;
|
|
9498
9972
|
}
|
|
9499
|
-
|
|
9500
|
-
|
|
9501
|
-
return `${home} ${scoreline(m)} ${away} ${minute}`;
|
|
9973
|
+
if (at < line.length || parts.length === 0) parts.push({ text: line.slice(at) });
|
|
9974
|
+
return parts.map((p) => p.text).join("") === line ? parts : [{ text: line }];
|
|
9502
9975
|
}
|
|
9503
9976
|
var MAX_LIVE_CONSIDERED = 64;
|
|
9504
9977
|
var MAX_LIVE_EXAMINED = 512;
|
|
@@ -9536,13 +10009,17 @@ function sealFixtures(raw, kind) {
|
|
|
9536
10009
|
};
|
|
9537
10010
|
}
|
|
9538
10011
|
function liveMatchesFromCache(state, nowMs, kind) {
|
|
10012
|
+
return liveRecordsFromCache(state, nowMs, kind).list;
|
|
10013
|
+
}
|
|
10014
|
+
function liveRecordsFromCache(state, nowMs, kind) {
|
|
9539
10015
|
const fresh = state && ageMs(state, nowMs) < DISPLAY_STALE_MS;
|
|
9540
10016
|
const rawLive = fresh ? state?.live : [];
|
|
9541
10017
|
if (!Array.isArray(rawLive)) {
|
|
9542
|
-
return { items: [], total: 0, shown: 0, truncated: false, complete: false };
|
|
10018
|
+
return { list: { items: [], total: 0, shown: 0, truncated: false, complete: false }, sources: [] };
|
|
9543
10019
|
}
|
|
9544
10020
|
const liveArr = rawLive;
|
|
9545
10021
|
const out2 = [];
|
|
10022
|
+
const sources = [];
|
|
9546
10023
|
let inspected = 0;
|
|
9547
10024
|
let readable = true;
|
|
9548
10025
|
for (let i = 0; i < liveArr.length; i++) {
|
|
@@ -9559,27 +10036,38 @@ function liveMatchesFromCache(state, nowMs, kind) {
|
|
|
9559
10036
|
continue;
|
|
9560
10037
|
}
|
|
9561
10038
|
const sealed = parsedValue(parseCachedMatch(m, { events: false, teamKind: kind }));
|
|
9562
|
-
if (sealed)
|
|
9563
|
-
|
|
10039
|
+
if (sealed) {
|
|
10040
|
+
out2.push(sealed);
|
|
10041
|
+
sources.push(raw);
|
|
10042
|
+
} else readable = false;
|
|
9564
10043
|
}
|
|
9565
10044
|
const exhausted = inspected === liveArr.length;
|
|
9566
10045
|
const complete = exhausted && readable;
|
|
9567
10046
|
return {
|
|
9568
|
-
|
|
9569
|
-
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
|
|
10047
|
+
list: {
|
|
10048
|
+
items: out2,
|
|
10049
|
+
total: out2.length,
|
|
10050
|
+
shown: out2.length,
|
|
10051
|
+
truncated: !exhausted,
|
|
10052
|
+
// False when we stopped early or a record was unreadable. In either case an
|
|
10053
|
+
// empty list must not render as the authoritative "nothing is on".
|
|
10054
|
+
complete
|
|
10055
|
+
},
|
|
10056
|
+
sources
|
|
9575
10057
|
};
|
|
9576
10058
|
}
|
|
9577
10059
|
var DEFAULT_MAX_SEGMENTS = 8;
|
|
9578
10060
|
var MAX_LINE_COLUMNS = 200;
|
|
9579
10061
|
function renderPrompt(state, opts = {}) {
|
|
9580
|
-
return
|
|
10062
|
+
return promptLine(state, opts).line;
|
|
10063
|
+
}
|
|
10064
|
+
function promptLine(state, opts = {}, played) {
|
|
10065
|
+
const rendered = renderPromptLine(state, opts, played);
|
|
10066
|
+
const unfitted = rendered.text;
|
|
10067
|
+
const line = truncateVisible(unfitted, lineColumns(opts));
|
|
10068
|
+
return { line, unfitted, parts: partsOf(line, keptRanges(unfitted, line, rendered.strong)) };
|
|
9581
10069
|
}
|
|
9582
|
-
function renderPromptLine(state, opts = {}) {
|
|
10070
|
+
function renderPromptLine(state, opts = {}, played) {
|
|
9583
10071
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
9584
10072
|
const nowMs = now.getTime();
|
|
9585
10073
|
const defaultCompetition = opts.defaultCompetition ?? true;
|
|
@@ -9589,52 +10077,89 @@ function renderPromptLine(state, opts = {}) {
|
|
|
9589
10077
|
const kind = opts.teamKind ?? defaultTeamKind(defaultCompetition);
|
|
9590
10078
|
const liveList = liveMatchesFromCache(state, nowMs, kind);
|
|
9591
10079
|
const live = liveList.items;
|
|
9592
|
-
const
|
|
9593
|
-
const
|
|
9594
|
-
const bundle = defaultCompetition ? allFixtures() : [];
|
|
9595
|
-
const schedule = cachedFixtures.length ? mergeLive(bundle, cachedFixtures) : defaultCompetition ? void 0 : [];
|
|
10080
|
+
const { cachedFixtures, schedule } = countdownSchedule(state, defaultCompetition, kind);
|
|
10081
|
+
const width = lineColumns(opts);
|
|
9596
10082
|
if (live.length > 0) {
|
|
9597
10083
|
const ordered = pickAmbientMatch(live, pick2);
|
|
9598
10084
|
const picked = ordered[0] !== void 0 && isPicked(ordered[0], pick2);
|
|
9599
10085
|
const max = picked ? 1 : opts.max && opts.max > 0 ? Math.min(opts.max, DEFAULT_MAX_SEGMENTS) : DEFAULT_MAX_SEGMENTS;
|
|
9600
10086
|
const shown = ordered.slice(0, max);
|
|
9601
10087
|
const overflow = live.length - shown.length;
|
|
9602
|
-
|
|
9603
|
-
const body = "\u26BD " + shown.map((m) => matchSegment(m, compact, flags)).join(" \xB7 ");
|
|
9604
|
-
return truncateVisible(body, MAX_LINE_COLUMNS - displayWidth(marker)) + marker;
|
|
10088
|
+
return fitLiveLine(shown.map((m) => matchSegment(m, compact, flags, opts.lang)), overflow, liveList.complete, width);
|
|
9605
10089
|
}
|
|
9606
|
-
const
|
|
9607
|
-
if (
|
|
9608
|
-
if (scheduleGateOpen(scheduleView(state?.schedule, nowMs), nowMs, { probe: false })) {
|
|
9609
|
-
const win = pickAmbientMatch(
|
|
9610
|
-
cachedFixtures.filter((m) => {
|
|
9611
|
-
const k = Date.parse(m.kickoff);
|
|
9612
|
-
return k <= nowMs && nowMs < k + LIVE_WINDOW_MS;
|
|
9613
|
-
}).sort(byKickoff),
|
|
9614
|
-
pick2
|
|
9615
|
-
);
|
|
9616
|
-
const first = win[0];
|
|
9617
|
-
const matchup = first && isResolvedFixture(first) ? `${teamTok(first.home, flags)} vs ${teamTok(first.away, flags)} ` : "";
|
|
9618
|
-
const more = win.length - 1;
|
|
9619
|
-
return `\u26BD ${matchup}live \xB7 syncing\u2026` + (more > 0 ? ` +${more}` : "");
|
|
9620
|
-
}
|
|
9621
|
-
} else if (!cacheFresh || !liveList.complete) {
|
|
9622
|
-
const win = pickAmbientMatch(fixturesInLiveWindow(nowMs, schedule), pick2);
|
|
10090
|
+
const win = syncingWindow(state, nowMs, liveWhole(liveList.complete, state), defaultCompetition, cachedFixtures, schedule, pick2);
|
|
10091
|
+
if (win) {
|
|
9623
10092
|
const first = win[0];
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
|
|
9628
|
-
}
|
|
10093
|
+
const matchup = first && isResolvedFixture(first) ? `${teamTok(first.home, flags)} vs ${teamTok(first.away, flags)} ` : "";
|
|
10094
|
+
const more = win.length - 1;
|
|
10095
|
+
const syncing = opts.lang === void 0 ? "live \xB7 syncing\u2026" : t(opts.lang, "ambient.syncing");
|
|
10096
|
+
return plain(fitWithMarker(`\u26BD ${matchup}${syncing}`, more > 0 ? ` +${more}` : "", width));
|
|
9629
10097
|
}
|
|
9630
|
-
const next =
|
|
10098
|
+
const next = countdownFixture(nowMs, schedule, pick2, played ?? playRecordIds(state?.play, nowMs));
|
|
9631
10099
|
if (next) {
|
|
9632
|
-
|
|
10100
|
+
const matchup = `${teamTok(next.home, flags)} vs ${teamTok(next.away, flags)}`;
|
|
10101
|
+
if (opts.lang !== void 0) return plain(`\u26BD ${matchup} ${countdownPhrase(opts.lang, next.kickoff, now)}`);
|
|
10102
|
+
const left = countdown(next.kickoff, now);
|
|
10103
|
+
return plain(left === COUNTDOWN_NOW ? `\u26BD ${matchup} ${COUNTDOWN_NOW}` : `\u26BD ${matchup} in ${left}`);
|
|
9633
10104
|
}
|
|
9634
10105
|
if (defaultCompetition && isTournamentWindowOver(nowMs, schedule)) {
|
|
9635
|
-
return TOURNAMENT_COMPLETE_LINE;
|
|
10106
|
+
return plain(TOURNAMENT_COMPLETE_LINE);
|
|
10107
|
+
}
|
|
10108
|
+
return plain(NOTHING_KNOWN_LINE);
|
|
10109
|
+
}
|
|
10110
|
+
function lineColumns(opts) {
|
|
10111
|
+
const c = opts.columns;
|
|
10112
|
+
return typeof c === "number" && c >= 1 ? Math.min(Math.floor(c), MAX_LINE_COLUMNS) : MAX_LINE_COLUMNS;
|
|
10113
|
+
}
|
|
10114
|
+
function fitWithMarker(body, marker, width) {
|
|
10115
|
+
const markerWidth = displayWidth(marker);
|
|
10116
|
+
if (markerWidth > width) return truncateVisible(marker, width);
|
|
10117
|
+
const room = width - markerWidth;
|
|
10118
|
+
return (room < 1 ? "" : truncateVisible(body, room)) + marker;
|
|
10119
|
+
}
|
|
10120
|
+
function fitLiveLine(segments, overflow, complete, width) {
|
|
10121
|
+
const markerFor = (dropped) => !complete ? " +more" : overflow + dropped > 0 ? ` +${overflow + dropped}` : "";
|
|
10122
|
+
const bodyOf = (kept2) => "\u26BD " + segments.slice(0, kept2).map((g) => g.text).join(" \xB7 ");
|
|
10123
|
+
let kept = segments.length;
|
|
10124
|
+
while (kept > 1 && displayWidth(bodyOf(kept)) + displayWidth(markerFor(segments.length - kept)) > width) kept--;
|
|
10125
|
+
const body = bodyOf(kept);
|
|
10126
|
+
const text = fitWithMarker(body, markerFor(segments.length - kept), width);
|
|
10127
|
+
const ranges = [];
|
|
10128
|
+
let offset = "\u26BD ".length;
|
|
10129
|
+
for (const g of segments.slice(0, kept)) {
|
|
10130
|
+
ranges.push([offset + g.score[0], offset + g.score[1]]);
|
|
10131
|
+
offset += g.text.length + " \xB7 ".length;
|
|
10132
|
+
}
|
|
10133
|
+
return { text, strong: keptRanges(body, text, ranges).filter(([, end]) => end <= text.length) };
|
|
10134
|
+
}
|
|
10135
|
+
function countdownSchedule(state, defaultCompetition, kind) {
|
|
10136
|
+
const cachedFixtureList = sealFixtures(defaultCompetition ? state?.fixtures : state?.schedule?.fixtures, kind);
|
|
10137
|
+
const cachedFixtures = defaultCompetition ? [...cachedFixtureList.items] : cachedFixtureList.items.filter((m) => hasLiveWindow(m.status));
|
|
10138
|
+
const bundle = defaultCompetition ? allFixtures() : [];
|
|
10139
|
+
const schedule = cachedFixtures.length ? mergeLive(bundle, cachedFixtures) : defaultCompetition ? void 0 : [];
|
|
10140
|
+
return { cachedFixtures, schedule };
|
|
10141
|
+
}
|
|
10142
|
+
function liveWhole(scanComplete, state) {
|
|
10143
|
+
return scanComplete && state?.liveComplete !== false;
|
|
10144
|
+
}
|
|
10145
|
+
function syncingWindow(state, nowMs, liveComplete, defaultCompetition, cachedFixtures, schedule, pick2) {
|
|
10146
|
+
const cacheFresh = !!state && state.degraded !== true && ageMs(state, nowMs) < DISPLAY_STALE_MS;
|
|
10147
|
+
if (cacheFresh && liveComplete) return void 0;
|
|
10148
|
+
if (!defaultCompetition) {
|
|
10149
|
+
if (!scheduleGateOpen(scheduleView(state?.schedule, nowMs), nowMs, { probe: false })) return void 0;
|
|
10150
|
+
return pickAmbientMatch(
|
|
10151
|
+
cachedFixtures.filter((m) => {
|
|
10152
|
+
const k = Date.parse(m.kickoff);
|
|
10153
|
+
return k <= nowMs && nowMs < k + LIVE_WINDOW_MS;
|
|
10154
|
+
}).sort(byKickoff),
|
|
10155
|
+
pick2
|
|
10156
|
+
);
|
|
9636
10157
|
}
|
|
9637
|
-
|
|
10158
|
+
const win = pickAmbientMatch(fixturesInLiveWindow(nowMs, schedule), pick2);
|
|
10159
|
+
return win.length > 0 ? win : void 0;
|
|
10160
|
+
}
|
|
10161
|
+
function countdownFixture(nowMs, schedule, pick2, played) {
|
|
10162
|
+
return pickAmbientMatch(countdownCandidates(nowMs, schedule, played), pick2)[0];
|
|
9638
10163
|
}
|
|
9639
10164
|
|
|
9640
10165
|
// src/hook.ts
|
|
@@ -9652,8 +10177,12 @@ function rosterPinned(t2, pin) {
|
|
|
9652
10177
|
const { team } = lookupTeam(t2.code);
|
|
9653
10178
|
return team ? { ...t2, name: team.name, flag: team.flag } : t2;
|
|
9654
10179
|
}
|
|
9655
|
-
function
|
|
9656
|
-
|
|
10180
|
+
function hookNames(m, pin) {
|
|
10181
|
+
return { home: rosterPinned(m.home, pin).name, away: rosterPinned(m.away, pin).name };
|
|
10182
|
+
}
|
|
10183
|
+
function hookLine(m, opts) {
|
|
10184
|
+
const { flags, pin } = opts;
|
|
10185
|
+
const minute = m.status === "FT" ? "FT" : m.status === "HT" ? "half-time" : m.minute ? `${m.minute}'` : "live";
|
|
9657
10186
|
const h = rosterPinned(m.home, pin);
|
|
9658
10187
|
const a = rosterPinned(m.away, pin);
|
|
9659
10188
|
const home = flags ? withFlag(h.name, h.flag, "home") : h.name;
|
|
@@ -9670,13 +10199,100 @@ function renderHook(state, opts = {}) {
|
|
|
9670
10199
|
const live = pickAmbientMatch(liveList.items, opts.pick);
|
|
9671
10200
|
const shown = live.slice(0, MAX_HOOK_MATCHES);
|
|
9672
10201
|
const overflow = live.length - shown.length;
|
|
9673
|
-
const lines = shown.map((mm) =>
|
|
10202
|
+
const lines = shown.map((mm) => hookLine(mm, { flags, pin })).join("\n");
|
|
9674
10203
|
const more = !liveList.complete ? "\n(more live matches may not be shown)" : overflow > 0 ? `
|
|
9675
10204
|
(+${overflow} more not shown)` : "";
|
|
9676
10205
|
return boundContext(`[Claudinho \u2014 live football scores right now]
|
|
9677
10206
|
${lines}`, more);
|
|
9678
10207
|
}
|
|
9679
10208
|
|
|
10209
|
+
// src/ambient.ts
|
|
10210
|
+
function pinnedSideOf(m, pin) {
|
|
10211
|
+
if (pin === void 0) return null;
|
|
10212
|
+
if (isPinnedSide(m.home, pin)) return "home";
|
|
10213
|
+
return isPinnedSide(m.away, pin) ? "away" : null;
|
|
10214
|
+
}
|
|
10215
|
+
var SCORING2 = /* @__PURE__ */ new Set(["GOAL", "OWN_GOAL", "PEN"]);
|
|
10216
|
+
function lastGoalOf(m, raw) {
|
|
10217
|
+
if (raw === void 0 || raw === null) return null;
|
|
10218
|
+
const e = sealEvent(raw);
|
|
10219
|
+
if (!e || !SCORING2.has(e.type)) return null;
|
|
10220
|
+
if (e.teamCode !== (e.side === "home" ? m.home.code : m.away.code)) return null;
|
|
10221
|
+
return (m.score?.[e.side] ?? 0) >= 1 ? e : null;
|
|
10222
|
+
}
|
|
10223
|
+
function finishedFromCache(state, nowMs, kind, live) {
|
|
10224
|
+
if (state?.liveComplete !== true) return [];
|
|
10225
|
+
const play = state.play;
|
|
10226
|
+
if (!play || typeof play !== "object" || Array.isArray(play)) return [];
|
|
10227
|
+
const out2 = [];
|
|
10228
|
+
const ids = /* @__PURE__ */ new Set();
|
|
10229
|
+
for (const raw of takeBounded(play.finished, MAX_PLAY)) {
|
|
10230
|
+
if (!raw || typeof raw !== "object") continue;
|
|
10231
|
+
const { match, at } = raw;
|
|
10232
|
+
if (!validStamp(at)) continue;
|
|
10233
|
+
const atMs = Date.parse(at);
|
|
10234
|
+
if (atMs > nowMs || nowMs - atMs >= FINISHED_TTL_MS) continue;
|
|
10235
|
+
const sealed = parsedValue(parseCachedMatch(match, { events: false, teamKind: kind }));
|
|
10236
|
+
if (sealed?.status !== "FT" || !sealed.score || live.has(sealed.id) || ids.has(sealed.id)) continue;
|
|
10237
|
+
ids.add(sealed.id);
|
|
10238
|
+
out2.push(sealed);
|
|
10239
|
+
}
|
|
10240
|
+
return out2;
|
|
10241
|
+
}
|
|
10242
|
+
function ambientView(state, opts = {}) {
|
|
10243
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
10244
|
+
const nowMs = now.getTime();
|
|
10245
|
+
const defaultCompetition = opts.defaultCompetition ?? true;
|
|
10246
|
+
const kind = opts.teamKind ?? defaultTeamKind(opts.defaultCompetition);
|
|
10247
|
+
const pin = opts.pin ?? (opts.pick && "team" in opts.pick ? opts.pick.team : void 0);
|
|
10248
|
+
const { lang, ...rest } = opts;
|
|
10249
|
+
const read = { ...rest, now };
|
|
10250
|
+
const hookOpts = { flags: opts.flags ?? true, pin: defaultCompetition };
|
|
10251
|
+
const { list, sources } = liveRecordsFromCache(state, nowMs, kind);
|
|
10252
|
+
const lastGoals = /* @__PURE__ */ new Map();
|
|
10253
|
+
list.items.forEach((m, i) => {
|
|
10254
|
+
lastGoals.set(m, lastGoalOf(m, sources[i]?.lastGoal));
|
|
10255
|
+
});
|
|
10256
|
+
const items = pickAmbientMatch(list.items, opts.pick).map((m) => ({
|
|
10257
|
+
...m,
|
|
10258
|
+
picked: isPicked(m, opts.pick),
|
|
10259
|
+
pinned: pin !== void 0 && (isPinnedSide(m.home, pin) || isPinnedSide(m.away, pin)),
|
|
10260
|
+
pinnedSide: pinnedSideOf(m, pin),
|
|
10261
|
+
names: hookNames(m, defaultCompetition),
|
|
10262
|
+
line: hookLine(m, hookOpts),
|
|
10263
|
+
lastGoal: lastGoals.get(m) ?? null
|
|
10264
|
+
}));
|
|
10265
|
+
const played = playRecordIds(state?.play, nowMs);
|
|
10266
|
+
const prompt = promptLine(state, { ...read, lang }, played);
|
|
10267
|
+
const context = renderHook(state, read);
|
|
10268
|
+
const { cachedFixtures, schedule } = countdownSchedule(state, defaultCompetition, kind);
|
|
10269
|
+
const age = stampAgeMs(state?.updatedAt, nowMs);
|
|
10270
|
+
const stamp = state && Number.isFinite(age) ? Date.parse(state.updatedAt) : void 0;
|
|
10271
|
+
const staleAt = stamp === void 0 ? void 0 : new Date(stamp + DISPLAY_STALE_MS);
|
|
10272
|
+
const finished = finishedFromCache(
|
|
10273
|
+
state,
|
|
10274
|
+
nowMs,
|
|
10275
|
+
kind,
|
|
10276
|
+
new Set(list.items.map((m) => m.id))
|
|
10277
|
+
).map((m) => ({ ...m, pinnedSide: pinnedSideOf(m, pin), names: hookNames(m, defaultCompetition), line: hookLine(m, hookOpts) }));
|
|
10278
|
+
const syncing = list.items.length === 0 && syncingWindow(state, nowMs, liveWhole(list.complete, state), defaultCompetition, cachedFixtures, schedule, opts.pick) !== void 0;
|
|
10279
|
+
return {
|
|
10280
|
+
line: prompt.line,
|
|
10281
|
+
lineParts: prompt.parts,
|
|
10282
|
+
idle: prompt.unfitted === TOURNAMENT_COMPLETE_LINE,
|
|
10283
|
+
empty: prompt.unfitted === NOTHING_KNOWN_LINE,
|
|
10284
|
+
context: context === "" ? null : context,
|
|
10285
|
+
live: { items, total: list.total, shown: list.shown, truncated: list.truncated, complete: list.complete },
|
|
10286
|
+
finished,
|
|
10287
|
+
current: stamp !== void 0 && age < DISPLAY_STALE_MS && state?.degraded !== true && state?.liveComplete === true && !syncing,
|
|
10288
|
+
next: countdownFixture(nowMs, schedule, opts.pick, played) ?? null,
|
|
10289
|
+
degraded: state?.degraded === true,
|
|
10290
|
+
source: typeof state?.source === "string" ? state.source : null,
|
|
10291
|
+
updatedAt: stamp === void 0 ? null : new Date(stamp).toISOString(),
|
|
10292
|
+
staleAfter: staleAt === void 0 || Number.isNaN(staleAt.getTime()) ? null : staleAt.toISOString()
|
|
10293
|
+
};
|
|
10294
|
+
}
|
|
10295
|
+
|
|
9680
10296
|
// src/refresh.ts
|
|
9681
10297
|
import { spawn } from "child_process";
|
|
9682
10298
|
var MIN_REFRESH_MS = 12e3;
|
|
@@ -9701,6 +10317,16 @@ function inKnockoutPhase(nowMs, competition) {
|
|
|
9701
10317
|
return !!next && isKnockoutStage(next.stage);
|
|
9702
10318
|
}
|
|
9703
10319
|
var NEVER = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
10320
|
+
function carriedPlay(base, now, kind) {
|
|
10321
|
+
return advancePlay(base?.play, NO_READ, now, kind);
|
|
10322
|
+
}
|
|
10323
|
+
function liveWithLastGoal(matches) {
|
|
10324
|
+
return matches.map((m) => ({ ...m, lastGoal: provenLastGoal(m) }));
|
|
10325
|
+
}
|
|
10326
|
+
function playAfterRead(play, r, at, kind) {
|
|
10327
|
+
const whole = !r.degraded && r.complete === true;
|
|
10328
|
+
return advancePlay(play, { whole, matches: r.matches, finished: r.finished ?? [], finishedTruncated: r.finishedTruncated === true }, at, kind);
|
|
10329
|
+
}
|
|
9704
10330
|
function liveAdapter(source, competition, now) {
|
|
9705
10331
|
return makeAdapter(source, { competition, enrichGroups: false, now });
|
|
9706
10332
|
}
|
|
@@ -9721,10 +10347,14 @@ async function runRefresh(opts) {
|
|
|
9721
10347
|
{
|
|
9722
10348
|
updatedAt: now.toISOString(),
|
|
9723
10349
|
live: [],
|
|
10350
|
+
liveComplete: false,
|
|
10351
|
+
// nothing was read
|
|
9724
10352
|
degraded: true,
|
|
9725
10353
|
// no live provider served this scope — never claim otherwise
|
|
9726
10354
|
source,
|
|
9727
|
-
competition
|
|
10355
|
+
competition,
|
|
10356
|
+
play: { seen: [], finished: [] }
|
|
10357
|
+
// nothing was read, and no snapshot to carry one from
|
|
9728
10358
|
},
|
|
9729
10359
|
idle
|
|
9730
10360
|
);
|
|
@@ -9754,10 +10384,14 @@ async function runRefresh(opts) {
|
|
|
9754
10384
|
{
|
|
9755
10385
|
updatedAt: now.toISOString(),
|
|
9756
10386
|
live: [],
|
|
10387
|
+
liveComplete: false,
|
|
10388
|
+
// nothing was read
|
|
9757
10389
|
degraded: until !== void 0,
|
|
9758
10390
|
source,
|
|
9759
10391
|
competition,
|
|
9760
|
-
...until !== void 0 ? { backoffUntil: new Date(until).toISOString() } : {}
|
|
10392
|
+
...until !== void 0 ? { backoffUntil: new Date(until).toISOString() } : {},
|
|
10393
|
+
play: { seen: [], finished: [] }
|
|
10394
|
+
// nothing was read, and no snapshot to carry one from
|
|
9761
10395
|
},
|
|
9762
10396
|
idle,
|
|
9763
10397
|
nowMs
|
|
@@ -9796,8 +10430,11 @@ async function runRefresh(opts) {
|
|
|
9796
10430
|
const { needLive, needFixtures } = plan(base);
|
|
9797
10431
|
if (!needLive && !needFixtures) return;
|
|
9798
10432
|
let live = base?.live ?? [];
|
|
10433
|
+
let liveComplete = base ? base.liveComplete : false;
|
|
9799
10434
|
let degraded = base?.degraded ?? false;
|
|
9800
10435
|
let updatedAt = base?.updatedAt ?? now.toISOString();
|
|
10436
|
+
const kind = teamKind(competition);
|
|
10437
|
+
let play = carriedPlay(base, now, kind);
|
|
9801
10438
|
let fixtures = base?.fixtures;
|
|
9802
10439
|
let fixturesUpdatedAt = base?.fixturesUpdatedAt;
|
|
9803
10440
|
let fixturesAttemptedAt = base?.fixturesAttemptedAt;
|
|
@@ -9808,11 +10445,14 @@ async function runRefresh(opts) {
|
|
|
9808
10445
|
const adapter = liveAdapter(source, competition, clock);
|
|
9809
10446
|
if (needLive) {
|
|
9810
10447
|
try {
|
|
9811
|
-
const r = await
|
|
9812
|
-
live = r.matches;
|
|
10448
|
+
const r = await getLiveRead(adapter, now, play.seen.map((e) => e.id));
|
|
10449
|
+
live = liveWithLastGoal(r.matches);
|
|
10450
|
+
liveComplete = r.complete === true;
|
|
9813
10451
|
degraded = r.degraded;
|
|
10452
|
+
play = playAfterRead(play, r, now, kind);
|
|
9814
10453
|
if (!r.degraded) season = r.season;
|
|
9815
10454
|
} catch {
|
|
10455
|
+
liveComplete = false;
|
|
9816
10456
|
degraded = true;
|
|
9817
10457
|
}
|
|
9818
10458
|
updatedAt = now.toISOString();
|
|
@@ -9847,6 +10487,7 @@ async function runRefresh(opts) {
|
|
|
9847
10487
|
{
|
|
9848
10488
|
updatedAt,
|
|
9849
10489
|
live,
|
|
10490
|
+
...liveComplete !== void 0 ? { liveComplete } : {},
|
|
9850
10491
|
degraded,
|
|
9851
10492
|
source,
|
|
9852
10493
|
competition,
|
|
@@ -9856,7 +10497,8 @@ async function runRefresh(opts) {
|
|
|
9856
10497
|
...backoffUntil ? { backoffUntil } : {},
|
|
9857
10498
|
...season ? { season } : {},
|
|
9858
10499
|
// Stored with the slice it describes, and only while that slice exists.
|
|
9859
|
-
...fixtures && fixturesSeason ? { fixturesSeason } : {}
|
|
10500
|
+
...fixtures && fixturesSeason ? { fixturesSeason } : {},
|
|
10501
|
+
play
|
|
9860
10502
|
},
|
|
9861
10503
|
token,
|
|
9862
10504
|
// The cycle's clock, not the carried live stamp: the throttle a
|
|
@@ -9935,8 +10577,11 @@ async function refreshOffBundle(c) {
|
|
|
9935
10577
|
const clock = () => nowMs + (Date.now() - realStart);
|
|
9936
10578
|
const adapter = liveAdapter(source, competition, clock);
|
|
9937
10579
|
let live = base?.live ?? [];
|
|
10580
|
+
let liveComplete = base ? base.liveComplete : false;
|
|
9938
10581
|
let degraded = base?.degraded ?? false;
|
|
9939
10582
|
let updatedAt = base?.updatedAt ?? NEVER;
|
|
10583
|
+
const kind = teamKind(competition);
|
|
10584
|
+
let play = carriedPlay(base, now, kind);
|
|
9940
10585
|
let season = sealSeason(base?.season);
|
|
9941
10586
|
let index = view.index;
|
|
9942
10587
|
let display = [...sealFixtures(base?.schedule?.fixtures, teamKind(competition)).items];
|
|
@@ -9951,6 +10596,7 @@ async function refreshOffBundle(c) {
|
|
|
9951
10596
|
const snapshot = (backoffUntil2) => ({
|
|
9952
10597
|
updatedAt,
|
|
9953
10598
|
live,
|
|
10599
|
+
...liveComplete !== void 0 ? { liveComplete } : {},
|
|
9954
10600
|
degraded,
|
|
9955
10601
|
source,
|
|
9956
10602
|
competition,
|
|
@@ -9965,7 +10611,8 @@ async function refreshOffBundle(c) {
|
|
|
9965
10611
|
...scheduleSeason ? { season: scheduleSeason } : {},
|
|
9966
10612
|
...inPlayUntil !== void 0 ? { inPlayUntil: new Date(inPlayUntil).toISOString() } : {},
|
|
9967
10613
|
...probe ? { probe: true } : {}
|
|
9968
|
-
}
|
|
10614
|
+
},
|
|
10615
|
+
play
|
|
9969
10616
|
});
|
|
9970
10617
|
if (needDiscovery) {
|
|
9971
10618
|
const before = { attemptedAt, failures };
|
|
@@ -10004,13 +10651,16 @@ async function refreshOffBundle(c) {
|
|
|
10004
10651
|
if (ageMs(base, nowMs) >= MIN_REFRESH_MS) {
|
|
10005
10652
|
const liveAt = clock();
|
|
10006
10653
|
try {
|
|
10007
|
-
const r = await getLiveRead(adapter, now);
|
|
10008
|
-
live = r.matches;
|
|
10654
|
+
const r = await getLiveRead(adapter, now, play.seen.map((e) => e.id));
|
|
10655
|
+
live = liveWithLastGoal(r.matches);
|
|
10656
|
+
liveComplete = r.complete === true;
|
|
10009
10657
|
degraded = r.degraded;
|
|
10658
|
+
play = playAfterRead(play, r, new Date(liveAt), kind);
|
|
10010
10659
|
if (!r.degraded) season = r.season;
|
|
10011
10660
|
if (r.matches.length > 0) inPlayUntil = raiseInPlay(inPlayUntil, r.matches, nowMs);
|
|
10012
10661
|
else if (!r.degraded && r.complete) inPlayUntil = void 0;
|
|
10013
10662
|
} catch {
|
|
10663
|
+
liveComplete = false;
|
|
10014
10664
|
degraded = true;
|
|
10015
10665
|
}
|
|
10016
10666
|
updatedAt = new Date(liveAt).toISOString();
|
|
@@ -10127,6 +10777,9 @@ function manual(path, snippet, why) {
|
|
|
10127
10777
|
${snippet}` };
|
|
10128
10778
|
}
|
|
10129
10779
|
function readSettings(path, snippet) {
|
|
10780
|
+
return readSettingsOr(path, (why) => manual(path, snippet, why));
|
|
10781
|
+
}
|
|
10782
|
+
function readSettingsOr(path, refuse) {
|
|
10130
10783
|
let entry;
|
|
10131
10784
|
try {
|
|
10132
10785
|
entry = lstatSync3(path);
|
|
@@ -10134,15 +10787,15 @@ function readSettings(path, snippet) {
|
|
|
10134
10787
|
return {};
|
|
10135
10788
|
}
|
|
10136
10789
|
if (entry.isSymbolicLink() && !existsSync(path)) {
|
|
10137
|
-
return
|
|
10790
|
+
return refuse("Settings path is a symlink to a missing file:");
|
|
10138
10791
|
}
|
|
10139
10792
|
let parsed;
|
|
10140
10793
|
try {
|
|
10141
10794
|
parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
10142
10795
|
} catch {
|
|
10143
|
-
return
|
|
10796
|
+
return refuse("Could not parse");
|
|
10144
10797
|
}
|
|
10145
|
-
if (!isSettingsObject(parsed)) return
|
|
10798
|
+
if (!isSettingsObject(parsed)) return refuse("Not a JSON settings object:");
|
|
10146
10799
|
return parsed;
|
|
10147
10800
|
}
|
|
10148
10801
|
function isInitResult(v) {
|
|
@@ -10233,6 +10886,86 @@ function initHook(opts = {}) {
|
|
|
10233
10886
|
message: `Live-score hook configured in ${path}. Restart Claude Code; during matches, the score is injected into context on each prompt.`
|
|
10234
10887
|
};
|
|
10235
10888
|
}
|
|
10889
|
+
var PLUGIN_INSTALL_LINE = "/plugin install claudinho --marketplace arturogarrido/claudinho";
|
|
10890
|
+
var CLAUDE_MCP_ONELINER = "claude mcp add claudinho -- npx -y @claudinho/mcp";
|
|
10891
|
+
var PLUGIN_NEXT = `Install the plugin (in Claude Code), and the MCP tools (in a shell):
|
|
10892
|
+
${PLUGIN_INSTALL_LINE}
|
|
10893
|
+
${CLAUDE_MCP_ONELINER}`;
|
|
10894
|
+
var RUNS_CLAUDINHO = /claudinho/i;
|
|
10895
|
+
function initPlugin(opts = {}) {
|
|
10896
|
+
const path = opts.path ?? claudeSettingsPath();
|
|
10897
|
+
const refuse = (why) => ({
|
|
10898
|
+
action: "manual",
|
|
10899
|
+
path,
|
|
10900
|
+
message: `${why} ${path}. Nothing was changed: remove the "statusLine" whose command is "${DEFAULT_PROMPT_COMMAND}" and the "hooks.${CLAUDE_HOOK_EVENT}" command "${HOOK_COMMAND}" yourself, if they are there.
|
|
10901
|
+
${PLUGIN_NEXT}`
|
|
10902
|
+
});
|
|
10903
|
+
const parsed = readSettingsOr(path, refuse);
|
|
10904
|
+
if (isInitResult(parsed)) return parsed;
|
|
10905
|
+
const settings = parsed;
|
|
10906
|
+
if (settings.hooks !== void 0 && !isSettingsObject(settings.hooks)) {
|
|
10907
|
+
return refuse('Unexpected "hooks" shape in');
|
|
10908
|
+
}
|
|
10909
|
+
const hooks = settings.hooks;
|
|
10910
|
+
const eventSlot = hooks?.[CLAUDE_HOOK_EVENT];
|
|
10911
|
+
if (eventSlot !== void 0 && (!Array.isArray(eventSlot) || !validHookMatchers(eventSlot))) {
|
|
10912
|
+
return refuse(`Unexpected "hooks.${CLAUDE_HOOK_EVENT}" shape in`);
|
|
10913
|
+
}
|
|
10914
|
+
const left = /* @__PURE__ */ new Set();
|
|
10915
|
+
const removed = [];
|
|
10916
|
+
const statusLine = settings.statusLine;
|
|
10917
|
+
if (isSettingsObject(statusLine) && typeof statusLine.command === "string") {
|
|
10918
|
+
if (statusLine.command === DEFAULT_PROMPT_COMMAND) {
|
|
10919
|
+
delete settings.statusLine;
|
|
10920
|
+
removed.push("the statusline");
|
|
10921
|
+
} else if (RUNS_CLAUDINHO.test(statusLine.command)) {
|
|
10922
|
+
left.add(statusLine.command);
|
|
10923
|
+
}
|
|
10924
|
+
}
|
|
10925
|
+
if (hooks && Array.isArray(eventSlot)) {
|
|
10926
|
+
let dropped = 0;
|
|
10927
|
+
const matchers = [];
|
|
10928
|
+
for (const matcher of eventSlot) {
|
|
10929
|
+
const commands = matcher.hooks;
|
|
10930
|
+
if (!commands) {
|
|
10931
|
+
matchers.push(matcher);
|
|
10932
|
+
continue;
|
|
10933
|
+
}
|
|
10934
|
+
const kept = commands.filter((h) => h.command !== HOOK_COMMAND);
|
|
10935
|
+
for (const h of kept) if (typeof h.command === "string" && RUNS_CLAUDINHO.test(h.command)) left.add(h.command);
|
|
10936
|
+
dropped += commands.length - kept.length;
|
|
10937
|
+
if (kept.length === commands.length) matchers.push(matcher);
|
|
10938
|
+
else if (kept.length > 0) matchers.push({ ...matcher, hooks: kept });
|
|
10939
|
+
}
|
|
10940
|
+
if (dropped > 0) {
|
|
10941
|
+
removed.push("the live-score hook");
|
|
10942
|
+
if (matchers.length > 0) hooks[CLAUDE_HOOK_EVENT] = matchers;
|
|
10943
|
+
else {
|
|
10944
|
+
delete hooks[CLAUDE_HOOK_EVENT];
|
|
10945
|
+
if (Object.keys(hooks).length === 0) delete settings.hooks;
|
|
10946
|
+
}
|
|
10947
|
+
}
|
|
10948
|
+
}
|
|
10949
|
+
const named = left.size ? `
|
|
10950
|
+
Left in place (not the commands init claude writes; remove them yourself if the plugin replaces them):
|
|
10951
|
+
${[...left].map((c) => ` ${c}`).join("\n")}` : "";
|
|
10952
|
+
if (removed.length === 0) {
|
|
10953
|
+
return {
|
|
10954
|
+
action: "already",
|
|
10955
|
+
path,
|
|
10956
|
+
message: `Nothing init claude wrote is in ${path}; nothing was changed.${named}
|
|
10957
|
+
${PLUGIN_NEXT}`
|
|
10958
|
+
};
|
|
10959
|
+
}
|
|
10960
|
+
backupOnce(path);
|
|
10961
|
+
writeFileAtomic(path, JSON.stringify(settings, null, 2) + "\n", SETTINGS_WRITE);
|
|
10962
|
+
return {
|
|
10963
|
+
action: "written",
|
|
10964
|
+
path,
|
|
10965
|
+
message: `Removed ${removed.join(" and ")} that init claude wrote from ${path}.${named}
|
|
10966
|
+
${PLUGIN_NEXT}`
|
|
10967
|
+
};
|
|
10968
|
+
}
|
|
10236
10969
|
|
|
10237
10970
|
// src/providerBackoff.ts
|
|
10238
10971
|
function persistBackoff(source, competition, until, nowMs) {
|
|
@@ -10307,7 +11040,8 @@ function adapterServing({ cfg, adapter, now }, competition) {
|
|
|
10307
11040
|
}
|
|
10308
11041
|
var DEFAULT_ON_MARKET_OPTS = { deadlineMs: 2e3, timeoutMs: 2500 };
|
|
10309
11042
|
var MARKETS_CMD_OPTS = { deadlineMs: 12e3, timeoutMs: 6e3 };
|
|
10310
|
-
async function marketSignalsFor(ctx, matches,
|
|
11043
|
+
async function marketSignalsFor(ctx, matches, baseOpts, now, providerFactory = makeMarketProvider) {
|
|
11044
|
+
const opts = { ...baseOpts, now };
|
|
10311
11045
|
if (ctx.marketProvider) {
|
|
10312
11046
|
const b = await getMarketSignals(ctx.marketProvider, matches, opts);
|
|
10313
11047
|
return { signals: resolvedValues(b), complete: b.complete };
|
|
@@ -10318,7 +11052,12 @@ async function marketSignalsFor(ctx, matches, opts = {}, providerFactory = makeM
|
|
|
10318
11052
|
return { signals: resolvedValues(b), complete: b.complete };
|
|
10319
11053
|
}
|
|
10320
11054
|
const competition = ctx.cfg.competition;
|
|
10321
|
-
const { signals: cached, checked: cachedIds } = readMarketCache(
|
|
11055
|
+
const { signals: cached, checked: cachedIds } = readMarketCache(
|
|
11056
|
+
"polymarket",
|
|
11057
|
+
competition,
|
|
11058
|
+
Date.now(),
|
|
11059
|
+
now
|
|
11060
|
+
);
|
|
10322
11061
|
const result = /* @__PURE__ */ new Map();
|
|
10323
11062
|
const miss = [];
|
|
10324
11063
|
for (const m of matches) {
|
|
@@ -10340,12 +11079,11 @@ async function marketSignalsFor(ctx, matches, opts = {}, providerFactory = makeM
|
|
|
10340
11079
|
}
|
|
10341
11080
|
return { signals: result, complete };
|
|
10342
11081
|
}
|
|
10343
|
-
async function reliableMarketSignals(ctx, matches) {
|
|
11082
|
+
async function reliableMarketSignals(ctx, matches, now) {
|
|
10344
11083
|
if (ctx.cfg.markets === false) return { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
10345
|
-
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
10346
11084
|
const relevant = matches.filter((m) => marketRelevant(m, now));
|
|
10347
11085
|
if (relevant.length === 0) return { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
10348
|
-
const raw = await marketSignalsFor(ctx, relevant, DEFAULT_ON_MARKET_OPTS);
|
|
11086
|
+
const raw = await marketSignalsFor(ctx, relevant, DEFAULT_ON_MARKET_OPTS, now);
|
|
10349
11087
|
const out2 = /* @__PURE__ */ new Map();
|
|
10350
11088
|
for (const [id, s] of raw.signals) {
|
|
10351
11089
|
const m = relevant.find((x) => x.id === id);
|
|
@@ -10353,18 +11091,21 @@ async function reliableMarketSignals(ctx, matches) {
|
|
|
10353
11091
|
}
|
|
10354
11092
|
return { signals: out2, complete: raw.complete };
|
|
10355
11093
|
}
|
|
10356
|
-
async function reliableMarketSignalFor(ctx, match) {
|
|
10357
|
-
const result = await reliableMarketSignals(ctx, [match]);
|
|
11094
|
+
async function reliableMarketSignalFor(ctx, match, now) {
|
|
11095
|
+
const result = await reliableMarketSignals(ctx, [match], now);
|
|
10358
11096
|
return { signal: result.signals.get(match.id), complete: result.complete };
|
|
10359
11097
|
}
|
|
10360
|
-
function out(
|
|
10361
|
-
process.stdout.write(
|
|
11098
|
+
function out(line = "") {
|
|
11099
|
+
process.stdout.write(line + "\n");
|
|
10362
11100
|
}
|
|
10363
11101
|
function emitJson(data) {
|
|
10364
11102
|
out(JSON.stringify(data, null, 2));
|
|
10365
11103
|
}
|
|
10366
11104
|
function modeOut(cfg, c) {
|
|
10367
|
-
out(
|
|
11105
|
+
out(modeRow(cfg, c));
|
|
11106
|
+
}
|
|
11107
|
+
function modeRow(cfg, c) {
|
|
11108
|
+
return c.dim(` ${modeLine(cfg.selection, cfg.lang)}`);
|
|
10368
11109
|
}
|
|
10369
11110
|
function competitionKey(cfg) {
|
|
10370
11111
|
return selectionExtras(cfg.selection);
|
|
@@ -10443,13 +11184,16 @@ async function nextAsked(ctx, team, usage) {
|
|
|
10443
11184
|
const { cfg, t: t2 } = ctx;
|
|
10444
11185
|
const asked = teamAsked(team, cfg);
|
|
10445
11186
|
if (!asked) throw new InputError(usage);
|
|
10446
|
-
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
10447
11187
|
if ("pin" in asked) {
|
|
10448
11188
|
const code2 = bundleApplies(cfg.competition) ? asked.pin.code : asked.pin.name;
|
|
10449
|
-
|
|
11189
|
+
const adapter2 = adapterFor(ctx);
|
|
11190
|
+
const now2 = requestClock(adapter2, ctx.now);
|
|
11191
|
+
return { code: code2, next: await nextFixtureForPin(adapter2, asked.pin, now2), now: now2 };
|
|
10450
11192
|
}
|
|
10451
11193
|
const code = teamQuery(asked.query, usage, t2, cfg.competition);
|
|
10452
|
-
|
|
11194
|
+
const adapter = adapterFor(ctx);
|
|
11195
|
+
const now = requestClock(adapter, ctx.now);
|
|
11196
|
+
return { code, next: await getNextFixtureForTeam(adapter, code, now), now };
|
|
10453
11197
|
}
|
|
10454
11198
|
function nextEmptySentence(next, code, label2, cfg, t2) {
|
|
10455
11199
|
return verdictNotice(next, cfg.lang) ?? nextHorizonSentence(next, code, cfg.lang) ?? nextNoneReadSentence(next, code, cfg.lang) ?? (next.degraded ? t2("live.degraded") : t2("next.none", { team: label2 }));
|
|
@@ -10463,22 +11207,34 @@ function resolveEnvTeam(raw, competition) {
|
|
|
10463
11207
|
return /^[A-Za-z]{3}$/.test(raw) ? raw.toUpperCase() : void 0;
|
|
10464
11208
|
}
|
|
10465
11209
|
function ambientPick(cfg) {
|
|
11210
|
+
return ambientPreference(cfg).pick;
|
|
11211
|
+
}
|
|
11212
|
+
function ambientPreference(cfg) {
|
|
10466
11213
|
const raw = process.env.CLAUDINHO_TEAM;
|
|
10467
11214
|
if (raw) {
|
|
10468
11215
|
const code = resolveEnvTeam(raw, cfg.competition);
|
|
10469
|
-
return code ? { code } : void 0;
|
|
11216
|
+
return code ? { pick: { code }, unreadable: false } : { pick: void 0, unreadable: true };
|
|
10470
11217
|
}
|
|
10471
|
-
return cfg.pin ? { team: cfg.pin } : void 0;
|
|
11218
|
+
return { pick: cfg.pin ? { team: cfg.pin } : void 0, unreadable: false };
|
|
11219
|
+
}
|
|
11220
|
+
function promptLineEnv() {
|
|
11221
|
+
const compact = !["0", "false", "no"].includes(
|
|
11222
|
+
(process.env.CLAUDINHO_COMPACT ?? "").toLowerCase()
|
|
11223
|
+
);
|
|
11224
|
+
const maxRaw = Number.parseInt(process.env.CLAUDINHO_MAX ?? "", 10);
|
|
11225
|
+
const max = Number.isFinite(maxRaw) && maxRaw > 0 ? maxRaw : void 0;
|
|
11226
|
+
return { compact, max };
|
|
10472
11227
|
}
|
|
10473
11228
|
async function cmdToday(date, ctx) {
|
|
10474
11229
|
const { cfg, t: t2 } = ctx;
|
|
10475
11230
|
precheck(cfg, t2, date);
|
|
10476
11231
|
const adapter = adapterFor(ctx);
|
|
10477
|
-
const
|
|
11232
|
+
const now = requestClock(adapter, ctx.now);
|
|
11233
|
+
const targetDate = date ?? localDate(now.toISOString(), cfg.tz);
|
|
10478
11234
|
const day = await getMatchesForDate(adapter, targetDate, resolveTz(cfg.tz));
|
|
10479
11235
|
const { matches, degraded, source } = day;
|
|
10480
11236
|
const todays = fixturesByDate(targetDate, matches, cfg.tz);
|
|
10481
|
-
const market = await reliableMarketSignals(ctx, todays);
|
|
11237
|
+
const market = await reliableMarketSignals(ctx, todays, now);
|
|
10482
11238
|
if (cfg.json) {
|
|
10483
11239
|
emitJson({
|
|
10484
11240
|
date: targetDate,
|
|
@@ -10530,13 +11286,14 @@ async function cmdToday(date, ctx) {
|
|
|
10530
11286
|
const src = attribution.attributed ? dataSource(source, cfg.lang, c) : "";
|
|
10531
11287
|
if (src) out(src);
|
|
10532
11288
|
out(disclaimer(t2, c));
|
|
10533
|
-
endScoreCommand(ctx);
|
|
11289
|
+
endScoreCommand(ctx, now);
|
|
10534
11290
|
}
|
|
10535
11291
|
async function cmdLive(ctx) {
|
|
10536
11292
|
const { cfg, t: t2 } = ctx;
|
|
10537
11293
|
precheck(cfg, t2);
|
|
10538
11294
|
const adapter = adapterFor(ctx);
|
|
10539
|
-
const
|
|
11295
|
+
const now = requestClock(adapter, ctx.now);
|
|
11296
|
+
const live = await getLiveMatches(adapter, now);
|
|
10540
11297
|
const { matches, degraded, source } = live;
|
|
10541
11298
|
if (cfg.json) {
|
|
10542
11299
|
emitJson({ degraded, source: source ?? null, matches, ...verdictExtras(live), ...competitionKey(cfg) });
|
|
@@ -10564,12 +11321,12 @@ async function cmdLive(ctx) {
|
|
|
10564
11321
|
const src = dataSource(source, cfg.lang, c);
|
|
10565
11322
|
if (src) out(src);
|
|
10566
11323
|
out(disclaimer(t2, c));
|
|
10567
|
-
endScoreCommand(ctx);
|
|
11324
|
+
endScoreCommand(ctx, now);
|
|
10568
11325
|
}
|
|
10569
11326
|
async function cmdNext(team, ctx) {
|
|
10570
11327
|
const { cfg, t: t2 } = ctx;
|
|
10571
11328
|
precheck(cfg, t2);
|
|
10572
|
-
const { code, next } = await nextAsked(ctx, team, teamUsage("next", cfg));
|
|
11329
|
+
const { code, next, now } = await nextAsked(ctx, team, teamUsage("next", cfg));
|
|
10573
11330
|
const { fixture, degraded, source } = next;
|
|
10574
11331
|
const label2 = next.team?.name ?? next.query ?? code;
|
|
10575
11332
|
if (cfg.json) {
|
|
@@ -10605,7 +11362,7 @@ async function cmdNext(team, ctx) {
|
|
|
10605
11362
|
}
|
|
10606
11363
|
out();
|
|
10607
11364
|
out(disclaimer(t2, c));
|
|
10608
|
-
endScoreCommand(ctx);
|
|
11365
|
+
endScoreCommand(ctx, now);
|
|
10609
11366
|
return;
|
|
10610
11367
|
}
|
|
10611
11368
|
for (const q of qualifiers) out(c.dim(" " + q));
|
|
@@ -10615,7 +11372,7 @@ async function cmdNext(team, ctx) {
|
|
|
10615
11372
|
out(
|
|
10616
11373
|
" " + c.dim(
|
|
10617
11374
|
joinSegments(
|
|
10618
|
-
isLive(fixture.status) ? [stage, when] : [stage, when, countdownPhrase(cfg.lang, fixture.kickoff)]
|
|
11375
|
+
isLive(fixture.status) ? [stage, when] : [stage, when, countdownPhrase(cfg.lang, fixture.kickoff, now)]
|
|
10619
11376
|
)
|
|
10620
11377
|
)
|
|
10621
11378
|
);
|
|
@@ -10623,7 +11380,7 @@ async function cmdNext(team, ctx) {
|
|
|
10623
11380
|
const src = dataSource(source, cfg.lang, c);
|
|
10624
11381
|
if (src) out(src);
|
|
10625
11382
|
out(disclaimer(t2, c));
|
|
10626
|
-
endScoreCommand(ctx);
|
|
11383
|
+
endScoreCommand(ctx, now);
|
|
10627
11384
|
}
|
|
10628
11385
|
function cmdTeam(query, ctx) {
|
|
10629
11386
|
const { cfg, t: t2 } = ctx;
|
|
@@ -10822,11 +11579,7 @@ function cmdPrompt({ cfg, now }, io = {}) {
|
|
|
10822
11579
|
out(renderPromptOutput("\u26BD \u2014", payload));
|
|
10823
11580
|
return;
|
|
10824
11581
|
}
|
|
10825
|
-
const compact
|
|
10826
|
-
(process.env.CLAUDINHO_COMPACT ?? "").toLowerCase()
|
|
10827
|
-
);
|
|
10828
|
-
const maxRaw = Number.parseInt(process.env.CLAUDINHO_MAX ?? "", 10);
|
|
10829
|
-
const max = Number.isFinite(maxRaw) && maxRaw > 0 ? maxRaw : void 0;
|
|
11582
|
+
const { compact, max } = promptLineEnv();
|
|
10830
11583
|
const state = readCurrentState(cfg.source, cfg.competition);
|
|
10831
11584
|
const scoreLine = renderPrompt(state, {
|
|
10832
11585
|
// CLAUDINHO_TEAM by code (offline lookup — hot-path safe), else the saved pin.
|
|
@@ -10873,6 +11626,79 @@ function cmdHook({ cfg, now }) {
|
|
|
10873
11626
|
} catch {
|
|
10874
11627
|
}
|
|
10875
11628
|
}
|
|
11629
|
+
var AMBIENT_EMPTY = JSON.stringify({ line: NOTHING_KNOWN_LINE, empty: true });
|
|
11630
|
+
function cmdAmbient({ cfg, now }, opts = {}) {
|
|
11631
|
+
let printed = false;
|
|
11632
|
+
try {
|
|
11633
|
+
if (cfg.selection.kind === "none") {
|
|
11634
|
+
out(
|
|
11635
|
+
JSON.stringify({
|
|
11636
|
+
...selectionExtras(cfg.selection),
|
|
11637
|
+
...verdictExtras(selectionVerdict(cfg.selection)),
|
|
11638
|
+
line: FIRST_RUN_LINE
|
|
11639
|
+
})
|
|
11640
|
+
);
|
|
11641
|
+
return;
|
|
11642
|
+
}
|
|
11643
|
+
if (cfg.selection.kind !== "selected") {
|
|
11644
|
+
out(AMBIENT_EMPTY);
|
|
11645
|
+
return;
|
|
11646
|
+
}
|
|
11647
|
+
const { compact, max } = promptLineEnv();
|
|
11648
|
+
const preference = ambientPreference(cfg);
|
|
11649
|
+
const lang = normalizeLang(cfg.lang);
|
|
11650
|
+
const state = readCurrentState(cfg.source, cfg.competition);
|
|
11651
|
+
const read = ambientView(state, {
|
|
11652
|
+
pick: preference.pick,
|
|
11653
|
+
// The SAVED pin, for each record's `pinned` (whatever CLAUDINHO_TEAM picks).
|
|
11654
|
+
pin: cfg.pin,
|
|
11655
|
+
compact,
|
|
11656
|
+
max,
|
|
11657
|
+
flags: flagsEnabled(),
|
|
11658
|
+
defaultCompetition: bundleApplies(cfg.competition),
|
|
11659
|
+
teamKind: teamKind(cfg.competition),
|
|
11660
|
+
now,
|
|
11661
|
+
columns: opts.columns,
|
|
11662
|
+
// The line's own tokens in the CLI's resolved language (the statusline module reads no environment).
|
|
11663
|
+
lang
|
|
11664
|
+
});
|
|
11665
|
+
const pick2 = preference.pick;
|
|
11666
|
+
const view = {
|
|
11667
|
+
line: read.line,
|
|
11668
|
+
lineParts: read.lineParts,
|
|
11669
|
+
idle: read.idle,
|
|
11670
|
+
empty: read.empty,
|
|
11671
|
+
context: read.context,
|
|
11672
|
+
live: read.live,
|
|
11673
|
+
finished: read.finished,
|
|
11674
|
+
current: read.current,
|
|
11675
|
+
next: read.next,
|
|
11676
|
+
pick: pick2 === void 0 ? null : "code" in pick2 ? { code: pick2.code } : pick2.team,
|
|
11677
|
+
...preference.unreadable ? { pickUnreadable: true } : {},
|
|
11678
|
+
// A selected competition: core's key is present (see `selectionExtras`).
|
|
11679
|
+
...selectionExtras(cfg.selection),
|
|
11680
|
+
// The language of the line and of the plugin's moment words (core's `moment.*` keys, templates).
|
|
11681
|
+
lang,
|
|
11682
|
+
words: momentWords(lang),
|
|
11683
|
+
degraded: read.degraded,
|
|
11684
|
+
source: read.source,
|
|
11685
|
+
updatedAt: read.updatedAt,
|
|
11686
|
+
staleAfter: read.staleAfter,
|
|
11687
|
+
disclaimer: DISCLAIMER,
|
|
11688
|
+
// The provider's sentence, English as every ambient string (never the reader's language): when the snapshot
|
|
11689
|
+
// names a provider and the line is neither the sign-off (`idle`) nor nothing known (`empty`); none otherwise.
|
|
11690
|
+
// `degraded` does not decide: a degraded snapshot still shows what an earlier read or discovery served.
|
|
11691
|
+
attribution: read.source !== null && !read.idle && !read.empty ? t("en", "live.data", { source: liveSourceLabel(read.source) }) : null
|
|
11692
|
+
};
|
|
11693
|
+
out(JSON.stringify(view));
|
|
11694
|
+
printed = true;
|
|
11695
|
+
if (refreshWanted(now?.getTime() ?? Date.now(), state, cfg.competition, cfg.source)) {
|
|
11696
|
+
spawnRefresh(cfg.source, cfg.competition);
|
|
11697
|
+
}
|
|
11698
|
+
} catch {
|
|
11699
|
+
if (!printed) out(AMBIENT_EMPTY);
|
|
11700
|
+
}
|
|
11701
|
+
}
|
|
10876
11702
|
async function cmdRefresh({ cfg }) {
|
|
10877
11703
|
if (cfg.selection.kind !== "selected") return;
|
|
10878
11704
|
await runRefresh({ source: cfg.source, competition: cfg.competition });
|
|
@@ -10911,7 +11737,6 @@ var CURSOR_MCP_SNIPPET = `{
|
|
|
10911
11737
|
"claudinho": { "command": "npx", "args": ["-y", "@claudinho/mcp"] }
|
|
10912
11738
|
}
|
|
10913
11739
|
}`;
|
|
10914
|
-
var CLAUDE_MCP_ONELINER = "claude mcp add claudinho -- npx -y @claudinho/mcp";
|
|
10915
11740
|
function cmdInitCursor(opts, { cfg }) {
|
|
10916
11741
|
if (opts.print) {
|
|
10917
11742
|
out("# 1) Cursor CLI statusline \u2192 ~/.cursor/cli-config.json");
|
|
@@ -10957,13 +11782,21 @@ function cmdInitClaude(opts, { cfg }) {
|
|
|
10957
11782
|
printInitChoose(cfg);
|
|
10958
11783
|
if (statusRes.action === "written" || hookRes.action === "written") printInitStarCta(cfg);
|
|
10959
11784
|
}
|
|
11785
|
+
function cmdInitPlugin({ cfg }) {
|
|
11786
|
+
printInitResult(initPlugin(), cfg);
|
|
11787
|
+
}
|
|
10960
11788
|
async function cmdMatch(id, ctx) {
|
|
10961
11789
|
const { cfg, t: t2 } = ctx;
|
|
10962
11790
|
precheck(cfg, t2);
|
|
10963
|
-
const
|
|
11791
|
+
const adapter = adapterFor(ctx);
|
|
11792
|
+
const now = requestClock(adapter, ctx.now);
|
|
11793
|
+
const found = await getMatchById(adapter, id, now);
|
|
10964
11794
|
const { match, degraded, source: liveSource } = found;
|
|
10965
|
-
const market = match ? await reliableMarketSignalFor(ctx, match) : { signal: void 0, complete: true };
|
|
11795
|
+
const market = match ? await reliableMarketSignalFor(ctx, match, now) : { signal: void 0, complete: true };
|
|
11796
|
+
const answer = matchAnswerRows(found, id, market, cfg, t2);
|
|
10966
11797
|
if (cfg.json) {
|
|
11798
|
+
const rows = [...answer.rows];
|
|
11799
|
+
while (rows.length && rowText(rows[rows.length - 1]) === "") rows.pop();
|
|
10967
11800
|
emitJson({
|
|
10968
11801
|
degraded,
|
|
10969
11802
|
match: match ?? null,
|
|
@@ -10975,59 +11808,91 @@ async function cmdMatch(id, ctx) {
|
|
|
10975
11808
|
// The span a whole read searched for an id it did not hold (a plain field).
|
|
10976
11809
|
...found.window ? { window: found.window } : {},
|
|
10977
11810
|
...verdictExtras(found),
|
|
11811
|
+
lines: rows.map(rowText),
|
|
11812
|
+
rows,
|
|
11813
|
+
attribution: answer.attribution || null,
|
|
11814
|
+
disclaimer: answer.disclaimer,
|
|
10978
11815
|
...competitionKey(cfg)
|
|
10979
11816
|
});
|
|
10980
11817
|
return;
|
|
10981
11818
|
}
|
|
10982
11819
|
const c = painterFor(cfg);
|
|
10983
|
-
const qualifiers = verdictQualifiers(found, cfg.lang);
|
|
10984
11820
|
out();
|
|
11821
|
+
for (const row of answer.rows) out(paintRow(row, c));
|
|
11822
|
+
if (answer.attribution) out(c.dim(answer.attribution));
|
|
11823
|
+
out(c.dim(answer.disclaimer));
|
|
11824
|
+
if (match) maybeStarNudge(ctx);
|
|
11825
|
+
}
|
|
11826
|
+
function rowText(row) {
|
|
11827
|
+
return row.parts.map((p) => p.text).join("");
|
|
11828
|
+
}
|
|
11829
|
+
function paintPart(p, c) {
|
|
11830
|
+
switch (p.style) {
|
|
11831
|
+
case "strong":
|
|
11832
|
+
return c.bold(p.text);
|
|
11833
|
+
case "dim":
|
|
11834
|
+
case "quote":
|
|
11835
|
+
return c.dim(p.text);
|
|
11836
|
+
case "accent":
|
|
11837
|
+
case "flair":
|
|
11838
|
+
return c.green(p.text);
|
|
11839
|
+
default:
|
|
11840
|
+
return p.text;
|
|
11841
|
+
}
|
|
11842
|
+
}
|
|
11843
|
+
function paintRow(row, c) {
|
|
11844
|
+
return row.parts.map((p) => paintPart(p, c)).join("");
|
|
11845
|
+
}
|
|
11846
|
+
function matchAnswerRows(found, id, market, cfg, t2) {
|
|
11847
|
+
const { match, degraded, source: liveSource } = found;
|
|
11848
|
+
const rows = [];
|
|
11849
|
+
const row = (role, ...parts) => rows.push({ role, parts });
|
|
11850
|
+
const blank = (role = "blank") => rows.push({ role, parts: [] });
|
|
11851
|
+
const plain2 = painterFor({ ...cfg, color: false });
|
|
11852
|
+
const footer = { disclaimer: disclaimer(t2, plain2) };
|
|
11853
|
+
const mode = { text: modeRow(cfg, plain2), style: "dim" };
|
|
11854
|
+
const qualifiers = verdictQualifiers(found, cfg.lang);
|
|
10985
11855
|
if (!match) {
|
|
10986
|
-
|
|
10987
|
-
for (const q of qualifiers)
|
|
10988
|
-
|
|
10989
|
-
|
|
10990
|
-
|
|
10991
|
-
|
|
10992
|
-
);
|
|
10993
|
-
|
|
10994
|
-
out(disclaimer(t2, c));
|
|
10995
|
-
return;
|
|
11856
|
+
row("text", mode);
|
|
11857
|
+
for (const q of qualifiers) row("text", { text: " " + q, style: "dim" });
|
|
11858
|
+
row("text", {
|
|
11859
|
+
text: " " + (degraded ? t2("live.degraded") : verdictNotice(found, cfg.lang) ?? matchWindowSentence(found, id, cfg.lang) ?? matchNoneReadSentence(found, id, cfg.lang) ?? t2("match.none", { id })),
|
|
11860
|
+
style: "dim"
|
|
11861
|
+
});
|
|
11862
|
+
blank("text");
|
|
11863
|
+
return { rows, attribution: "", ...footer };
|
|
10996
11864
|
}
|
|
10997
|
-
for (const q of qualifiers)
|
|
11865
|
+
for (const q of qualifiers) row("qualifier", { text: " " + q, style: "dim" });
|
|
10998
11866
|
const attribution = dayAttribution(found, [match], cfg.lang);
|
|
10999
|
-
if (attribution.unserved)
|
|
11000
|
-
|
|
11001
|
-
|
|
11867
|
+
if (attribution.unserved) row("qualifier", { text: " " + attribution.unserved, style: "dim" });
|
|
11868
|
+
const pin = cfg.pin;
|
|
11869
|
+
const side = (team) => pin !== void 0 && isPinnedSide(team, pin) ? { text: team.name, style: "accent" } : { text: team.name };
|
|
11870
|
+
row("title", side(match.home), { text: " " }, { text: scoreline(match), style: "strong" }, { text: " " }, side(match.away));
|
|
11871
|
+
row("mode", mode);
|
|
11002
11872
|
const where = joinSegments([stageLabelI18n(cfg.lang, match), matchLocation(match)]);
|
|
11003
|
-
if (where)
|
|
11004
|
-
|
|
11005
|
-
|
|
11006
|
-
|
|
11007
|
-
|
|
11008
|
-
);
|
|
11873
|
+
if (where) row("meta", { text: " " }, { text: where, style: "dim" });
|
|
11874
|
+
const word = statusWord(match, t2);
|
|
11875
|
+
const kickoff = { text: `${t2("match.kickoff")} ${formatKickoff(match.kickoff, { tz: cfg.tz, locale: cfg.lang })}`, style: "dim" };
|
|
11876
|
+
if (word) row("status", { text: " " }, { text: word, style: "strong" }, { text: " \xB7 ", style: "dim" }, kickoff);
|
|
11877
|
+
else row("status", { text: " " }, kickoff);
|
|
11009
11878
|
const flair = matchFlair(match, flairOpts(cfg));
|
|
11010
|
-
if (flair.text)
|
|
11011
|
-
if (match.events?.length) {
|
|
11012
|
-
out();
|
|
11013
|
-
for (const e of match.events) {
|
|
11014
|
-
out(` ${e.minute}' ${e.type} ${e.teamCode}${e.player ? ` \u2014 ${e.player}` : ""}`);
|
|
11015
|
-
}
|
|
11016
|
-
}
|
|
11879
|
+
if (flair.text) row("flair", { text: " " }, { text: flair.text, style: flair.rally ? "flair" : "quote" });
|
|
11017
11880
|
if (market.signal) {
|
|
11018
|
-
|
|
11019
|
-
for (const mline of marketBlock(market.signal, match))
|
|
11881
|
+
blank();
|
|
11882
|
+
for (const mline of marketBlock(market.signal, match)) row("market", { text: " " }, { text: mline, style: "dim" });
|
|
11883
|
+
}
|
|
11884
|
+
const timeline = timelineParts(match, { lang: cfg.lang, flags: flagsEnabled() });
|
|
11885
|
+
if (timeline.length) {
|
|
11886
|
+
blank();
|
|
11887
|
+
for (const parts of timeline) row("event", ...parts);
|
|
11020
11888
|
}
|
|
11021
11889
|
if (!market.complete) {
|
|
11022
|
-
|
|
11023
|
-
|
|
11890
|
+
blank();
|
|
11891
|
+
row("note", { text: " Market data unavailable or incomplete \u2014 this match could not be checked.", style: "dim" });
|
|
11024
11892
|
}
|
|
11025
|
-
|
|
11026
|
-
if (degraded)
|
|
11027
|
-
|
|
11028
|
-
if (src) out(src);
|
|
11029
|
-
out(disclaimer(t2, c));
|
|
11030
|
-
maybeStarNudge(ctx);
|
|
11893
|
+
blank();
|
|
11894
|
+
if (degraded) row("note", { text: " " + t2(found.earlierRecord ? "feed.earlierRecord" : "feed.degraded"), style: "dim" });
|
|
11895
|
+
return { rows, attribution: dataSource(liveSource, cfg.lang, plain2), ...footer };
|
|
11031
11896
|
}
|
|
11032
11897
|
var MARKET_INFO = "Prediction-market data is informational only.";
|
|
11033
11898
|
function marketHeaderLine(m, cfg) {
|
|
@@ -11040,7 +11905,7 @@ function noSignalLine(m, now, competition) {
|
|
|
11040
11905
|
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.";
|
|
11041
11906
|
}
|
|
11042
11907
|
function printMarketBlock(m, sig, c) {
|
|
11043
|
-
for (const
|
|
11908
|
+
for (const line of marketBlock(sig, m)) out(" " + c.dim(line));
|
|
11044
11909
|
}
|
|
11045
11910
|
async function cmdMarkets(target, team, ctx) {
|
|
11046
11911
|
const { cfg, t: t2 } = ctx;
|
|
@@ -11050,10 +11915,11 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
11050
11915
|
const asked = teamAsked(team, cfg);
|
|
11051
11916
|
if (!asked) throw new InputError(usage);
|
|
11052
11917
|
const code = "pin" in asked ? asked.pin.code : resolveTeamArg(asked.query, usage, t2, cfg.competition);
|
|
11053
|
-
const
|
|
11054
|
-
const
|
|
11918
|
+
const adapter2 = adapterFor(ctx);
|
|
11919
|
+
const now2 = requestClock(adapter2, ctx.now);
|
|
11920
|
+
const picked = await marketFixtureForTeam(adapter2, code, now2);
|
|
11055
11921
|
const { match: fixture, degraded } = picked;
|
|
11056
|
-
const market = fixture && marketRelevant(fixture, now2) ? await marketSignalsFor(ctx, [fixture], MARKETS_CMD_OPTS) : { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
11922
|
+
const market = fixture && marketRelevant(fixture, now2) ? await marketSignalsFor(ctx, [fixture], MARKETS_CMD_OPTS, now2) : { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
11057
11923
|
const sig = fixture ? market.signals.get(fixture.id) : void 0;
|
|
11058
11924
|
const shown = market.complete && fixture && sig && marketDisplayable(fixture, sig) ? sig : void 0;
|
|
11059
11925
|
if (cfg.json) {
|
|
@@ -11097,7 +11963,6 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
11097
11963
|
}
|
|
11098
11964
|
if (target && target !== "today" && !isValidDate(target)) {
|
|
11099
11965
|
precheck(cfg, t2);
|
|
11100
|
-
const now2 = ctx.now ?? /* @__PURE__ */ new Date();
|
|
11101
11966
|
const scope = marketScopeVerdict(cfg.competition, 0);
|
|
11102
11967
|
const outOfScope = verdictNotice(scope, cfg.lang);
|
|
11103
11968
|
if (outOfScope !== void 0) {
|
|
@@ -11121,9 +11986,11 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
11121
11986
|
out(c3.dim(MARKET_INFO));
|
|
11122
11987
|
return;
|
|
11123
11988
|
}
|
|
11124
|
-
const
|
|
11989
|
+
const adapter2 = adapterFor(ctx);
|
|
11990
|
+
const now2 = requestClock(adapter2, ctx.now);
|
|
11991
|
+
const found = await getMatchById(adapter2, target, now2);
|
|
11125
11992
|
const { match } = found;
|
|
11126
|
-
const market = match && marketRelevant(match, now2) ? await marketSignalsFor(ctx, [match], MARKETS_CMD_OPTS) : { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
11993
|
+
const market = match && marketRelevant(match, now2) ? await marketSignalsFor(ctx, [match], MARKETS_CMD_OPTS, now2) : { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
11127
11994
|
const sig = match ? market.signals.get(match.id) : void 0;
|
|
11128
11995
|
const shown = market.complete && match && sig && marketDisplayable(match, sig) ? sig : void 0;
|
|
11129
11996
|
if (cfg.json) {
|
|
@@ -11159,12 +12026,13 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
11159
12026
|
}
|
|
11160
12027
|
const explicitDate = target && target !== "today" ? target : void 0;
|
|
11161
12028
|
precheck(cfg, t2, explicitDate);
|
|
11162
|
-
const
|
|
12029
|
+
const adapter = adapterFor(ctx);
|
|
12030
|
+
const now = requestClock(adapter, ctx.now);
|
|
11163
12031
|
const date = explicitDate ?? localDate(now.toISOString(), cfg.tz);
|
|
11164
|
-
const day = await getMatchesForDate(
|
|
12032
|
+
const day = await getMatchesForDate(adapter, date, resolveTz(cfg.tz));
|
|
11165
12033
|
const todays = fixturesByDate(date, day.matches, cfg.tz);
|
|
11166
12034
|
const relevant = todays.filter((m) => marketRelevant(m, now));
|
|
11167
|
-
const { signals, complete } = await marketSignalsFor(ctx, relevant, MARKETS_CMD_OPTS);
|
|
12035
|
+
const { signals, complete } = await marketSignalsFor(ctx, relevant, MARKETS_CMD_OPTS, now);
|
|
11168
12036
|
const rows = relevant.map((m) => ({ match: m, signal: signals.get(m.id) })).filter(
|
|
11169
12037
|
(r) => !!r.signal && marketDisplayable(r.match, r.signal)
|
|
11170
12038
|
);
|
|
@@ -11213,8 +12081,8 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
11213
12081
|
function pickShareStyle(v) {
|
|
11214
12082
|
return v === "compact" ? "compact" : "social";
|
|
11215
12083
|
}
|
|
11216
|
-
async function reliableShareSignals(ctx, matches) {
|
|
11217
|
-
const raw = await reliableMarketSignals(ctx, matches);
|
|
12084
|
+
async function reliableShareSignals(ctx, matches, now) {
|
|
12085
|
+
const raw = await reliableMarketSignals(ctx, matches, now);
|
|
11218
12086
|
const out2 = /* @__PURE__ */ new Map();
|
|
11219
12087
|
for (const [id, s] of raw.signals) {
|
|
11220
12088
|
const m = matches.find((x) => x.id === id);
|
|
@@ -11315,7 +12183,8 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
11315
12183
|
const where = { tz: cfg.tz, locale: cfg.lang, competition: cfg.competition };
|
|
11316
12184
|
if (target === "live") {
|
|
11317
12185
|
precheck(cfg, t2);
|
|
11318
|
-
const
|
|
12186
|
+
const adapter2 = adapterFor(ctx);
|
|
12187
|
+
const live = await getLiveMatches(adapter2, requestClock(adapter2, ctx.now));
|
|
11319
12188
|
emitMatchCard(ctx, liveShareCard(live, where), { ...baseOptions, includeMarkets: false }, copy);
|
|
11320
12189
|
return;
|
|
11321
12190
|
}
|
|
@@ -11356,25 +12225,29 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
11356
12225
|
}
|
|
11357
12226
|
if (target === "next") {
|
|
11358
12227
|
precheck(cfg, t2);
|
|
11359
|
-
const { code, next } = await nextAsked(ctx, team, teamUsage("share next", cfg));
|
|
11360
|
-
const market2 = await reliableShareSignals(ctx, next.fixture ? [next.fixture] : []);
|
|
12228
|
+
const { code, next, now: now2 } = await nextAsked(ctx, team, teamUsage("share next", cfg));
|
|
12229
|
+
const market2 = await reliableShareSignals(ctx, next.fixture ? [next.fixture] : [], now2);
|
|
11361
12230
|
emitMatchCard(ctx, nextShareCard(next, code, market2, where), baseOptions, copy);
|
|
11362
12231
|
return;
|
|
11363
12232
|
}
|
|
11364
12233
|
if (target && target !== "today" && !isValidDate(target)) {
|
|
11365
12234
|
precheck(cfg, t2);
|
|
11366
|
-
const
|
|
11367
|
-
const
|
|
12235
|
+
const adapter2 = adapterFor(ctx);
|
|
12236
|
+
const now2 = requestClock(adapter2, ctx.now);
|
|
12237
|
+
const found = await getMatchById(adapter2, target, now2);
|
|
12238
|
+
const market2 = await reliableShareSignals(ctx, found.match ? [found.match] : [], now2);
|
|
11368
12239
|
emitMatchCard(ctx, matchShareCard(found, target, market2, where), baseOptions, copy);
|
|
11369
12240
|
return;
|
|
11370
12241
|
}
|
|
11371
12242
|
const explicitDate = target && target !== "today" ? target : void 0;
|
|
11372
12243
|
precheck(cfg, t2, explicitDate);
|
|
11373
|
-
const
|
|
11374
|
-
const
|
|
12244
|
+
const adapter = adapterFor(ctx);
|
|
12245
|
+
const now = requestClock(adapter, ctx.now);
|
|
12246
|
+
const date = explicitDate ?? localDate(now.toISOString(), cfg.tz);
|
|
12247
|
+
const day = await getMatchesForDate(adapter, date, resolveTz(cfg.tz));
|
|
11375
12248
|
const { matches: all, degraded, source } = day;
|
|
11376
12249
|
const todays = fixturesByDate(date, all, cfg.tz);
|
|
11377
|
-
const market = await reliableShareSignals(ctx, todays);
|
|
12250
|
+
const market = await reliableShareSignals(ctx, todays, now);
|
|
11378
12251
|
emitMatchCard(
|
|
11379
12252
|
ctx,
|
|
11380
12253
|
dateShareCard(
|
|
@@ -11462,7 +12335,8 @@ async function pinFor(ctx, competition, query) {
|
|
|
11462
12335
|
if (!isKnownSource(cfg.source)) {
|
|
11463
12336
|
throw new InputError(t2("err.source", { source: cfg.source, sources: KNOWN_SOURCES.join(", ") }));
|
|
11464
12337
|
}
|
|
11465
|
-
const
|
|
12338
|
+
const adapter = adapterServing(ctx, competition);
|
|
12339
|
+
const next = await getNextFixtureForTeam(adapter, label2, requestClock(adapter, ctx.now));
|
|
11466
12340
|
if (next.team) {
|
|
11467
12341
|
const { id, code, name } = next.team;
|
|
11468
12342
|
return id !== void 0 ? { id, code, name } : { code, name };
|
|
@@ -11655,8 +12529,8 @@ function maybeStarNudge(ctx) {
|
|
|
11655
12529
|
out();
|
|
11656
12530
|
out(c.dim(` \u2B50 Enjoying Claudinho? Star it \u2192 ${REPO_URL} (claudinho star)`));
|
|
11657
12531
|
}
|
|
11658
|
-
function endScoreCommand(ctx) {
|
|
11659
|
-
const over = bundleApplies(ctx.cfg.competition) && isTournamentWindowOver(
|
|
12532
|
+
function endScoreCommand(ctx, now) {
|
|
12533
|
+
const over = bundleApplies(ctx.cfg.competition) && isTournamentWindowOver(now.getTime());
|
|
11660
12534
|
if (over) printTournamentSignOff(ctx);
|
|
11661
12535
|
else maybeStarNudge(ctx);
|
|
11662
12536
|
}
|
|
@@ -11680,27 +12554,28 @@ function printInitStarCta(cfg) {
|
|
|
11680
12554
|
function cmdVibe(ctx) {
|
|
11681
12555
|
const { cfg } = ctx;
|
|
11682
12556
|
const bundled = cfg.selection.kind === "selected" && bundleApplies(cfg.competition);
|
|
11683
|
-
const
|
|
11684
|
-
const
|
|
12557
|
+
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
12558
|
+
const pool = vibePool(localDate(now.toISOString(), cfg.tz), bundled);
|
|
12559
|
+
const line = pool[Math.floor(Math.random() * pool.length)];
|
|
11685
12560
|
let liveSeg;
|
|
11686
12561
|
try {
|
|
11687
12562
|
if (cfg.selection.kind !== "selected") throw new Error("no competition");
|
|
11688
12563
|
const state = readCurrentState(cfg.source, cfg.competition);
|
|
11689
12564
|
liveSeg = vibeLiveSegment(
|
|
11690
12565
|
// Sealed with the competition's written kind, like the statusline's.
|
|
11691
|
-
liveMatchesFromCache(state,
|
|
12566
|
+
liveMatchesFromCache(state, now.getTime(), teamKind(cfg.competition)).items,
|
|
11692
12567
|
// The statusline's and the hook's pick (offline).
|
|
11693
12568
|
ambientPick(cfg)
|
|
11694
12569
|
);
|
|
11695
12570
|
} catch {
|
|
11696
12571
|
}
|
|
11697
12572
|
if (cfg.json) {
|
|
11698
|
-
emitJson({ vibe:
|
|
12573
|
+
emitJson({ vibe: line, tag: "#VibingLaVidaLoca", ...liveSeg ? { live: liveSeg } : {} });
|
|
11699
12574
|
return;
|
|
11700
12575
|
}
|
|
11701
12576
|
const c = painterFor(cfg);
|
|
11702
12577
|
out();
|
|
11703
|
-
out(" \u26BD " + (liveSeg ? `${liveSeg} \u2014 ` : "") + c.bold(
|
|
12578
|
+
out(" \u26BD " + (liveSeg ? `${liveSeg} \u2014 ` : "") + c.bold(line ?? ""));
|
|
11704
12579
|
out(" " + c.cyan("#VibingLaVidaLoca"));
|
|
11705
12580
|
out();
|
|
11706
12581
|
}
|
|
@@ -11714,7 +12589,7 @@ function handlePipeError(stream) {
|
|
|
11714
12589
|
}
|
|
11715
12590
|
handlePipeError(process.stdout);
|
|
11716
12591
|
handlePipeError(process.stderr);
|
|
11717
|
-
var VERSION = "0.
|
|
12592
|
+
var VERSION = "0.12.0";
|
|
11718
12593
|
var DISCLAIMER2 = `Claudinho is an ${FAN_PROJECT.toLowerCase()}. ${DISCLAIMER}`;
|
|
11719
12594
|
function ctxFrom(cmd) {
|
|
11720
12595
|
let root = cmd;
|
|
@@ -11813,7 +12688,10 @@ program.command("prompt").description("print a status line (Claude Code / Cursor
|
|
|
11813
12688
|
const cursor = await readCursorPayloadBounded();
|
|
11814
12689
|
cmdPrompt(ctxFrom(cmd), { cursor });
|
|
11815
12690
|
});
|
|
11816
|
-
|
|
12691
|
+
program.command("ambient").description("print the statusline and hook as one JSON object (for a program: the Claude Code plugin)").option("--columns <n>", "fit the line to this many display columns (a positive integer)", positiveInteger).action((opts, cmd) => {
|
|
12692
|
+
cmdAmbient(ctxFrom(cmd), { columns: opts.columns });
|
|
12693
|
+
});
|
|
12694
|
+
var init = program.command("init").description("one-step setup for your agent: `init cursor` or `init claude`; `init plugin` for the Claude Code plugin");
|
|
11817
12695
|
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) => {
|
|
11818
12696
|
try {
|
|
11819
12697
|
cmdInitCursor(opts, ctxFrom(cmd));
|
|
@@ -11828,6 +12706,13 @@ init.command("claude").description("set up claudinho for Claude Code (statusline
|
|
|
11828
12706
|
fail(e);
|
|
11829
12707
|
}
|
|
11830
12708
|
});
|
|
12709
|
+
init.command("plugin").description("remove the statusline and hook that init claude wrote, for the Claude Code plugin").action((_opts, cmd) => {
|
|
12710
|
+
try {
|
|
12711
|
+
cmdInitPlugin(ctxFrom(cmd));
|
|
12712
|
+
} catch (e) {
|
|
12713
|
+
fail(e);
|
|
12714
|
+
}
|
|
12715
|
+
});
|
|
11831
12716
|
program.command("init-statusline").description("configure the Claude Code statusline to use claudinho").option("--print", "print the settings snippet instead of writing it").option("--command <cmd>", "command to run (default: claudinho prompt)").action((opts, cmd) => {
|
|
11832
12717
|
try {
|
|
11833
12718
|
cmdInitStatusline(opts, ctxFrom(cmd));
|