@claudinho/cli 0.11.1 → 0.11.2
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 +22 -2
- package/dist/index.js +337 -72
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @claudinho/cli ⚽
|
|
2
2
|
|
|
3
|
-
**Live football scores, fixtures and standings in your terminal and your Claude Code and Cursor CLI statusline, for the competition you follow: the World Cup, the Premier League, LALIGA, the Champions League and 12 more.** The `claudinho` command: TZ-aware, localized, scriptable, with read-only market signals for the World Cup. No API key, no signup. It ran the 2026 World Cup from the opener to the final.
|
|
3
|
+
**Live football scores, fixtures and standings in your terminal and your Claude Code and Cursor CLI statusline (and, for a program, `claudinho ambient --json`: the statusline and the hook as one JSON object), for the competition you follow: the World Cup, the Premier League, LALIGA, the Champions League and 12 more.** The `claudinho` command: TZ-aware, localized, scriptable, with read-only market signals for the World Cup. No API key, no signup. It ran the 2026 World Cup from the opener to the final.
|
|
4
4
|
|
|
5
5
|
> ⭐ Installing via `npx` or globally? **[Star the repo](https://github.com/arturogarrido/claudinho)**: a fan project runs on stars. (`claudinho star` shows you how anytime.)
|
|
6
6
|
|
|
@@ -53,9 +53,11 @@ claudinho share [target] # copy-pasteable snippet: today | live | <date> | <i
|
|
|
53
53
|
claudinho prompt # one compact status line (for statusline/tmux/Starship)
|
|
54
54
|
claudinho init cursor # one-step Cursor setup: statusline + MCP paste (--print for snippets)
|
|
55
55
|
claudinho init claude # one-step Claude Code setup: statusline + hook + MCP one-liner
|
|
56
|
+
claudinho init plugin # remove the statusline and hook init claude wrote, for the Claude Code plugin
|
|
56
57
|
claudinho init-statusline # (granular) wire just the Claude Code statusline
|
|
57
58
|
claudinho init-cursor-statusline # (granular) wire just the Cursor CLI statusline
|
|
58
59
|
claudinho hook # live-score context for a Claude Code hook (silent off-match)
|
|
60
|
+
claudinho ambient --json # the statusline and the hook as one JSON object, for a program (--columns N)
|
|
59
61
|
claudinho init-hook # (granular) make Claude itself score-aware (UserPromptSubmit)
|
|
60
62
|
claudinho vibe # a matchday-coder one-liner (#VibingLaVidaLoca)
|
|
61
63
|
claudinho star # how to support the project (star the repo ⭐)
|
|
@@ -109,7 +111,7 @@ CLAUDINHO_COMPETITION=laliga claudinho table # the environment works to
|
|
|
109
111
|
| `--tz <zone>` | IANA timezone, e.g. `America/Mexico_City` (also `CLAUDINHO_TZ`; default: system). Kickoff times **and** which day a fixture falls on are computed in this zone: a late-night-UTC match shows on the day you actually watch it. |
|
|
110
112
|
| `--json` | machine-readable output for scripting |
|
|
111
113
|
| `--no-color` | disable ANSI color (also honors `NO_COLOR`; auto-off when piped) |
|
|
112
|
-
| `-c, --competition <alias\|slug>` | the competition: an alias such as `premier-league` (the sixteen competitions are listed in the [root README](https://github.com/arturogarrido/claudinho#competitions)), or an ESPN slug such as `eng.1` (any other lower-case dotted ESPN slug works too, labelled experimental). Also `CLAUDINHO_COMPETITION` (an alias or a slug), which the statusline
|
|
114
|
+
| `-c, --competition <alias\|slug>` | the competition: an alias such as `premier-league` (the sixteen competitions are listed in the [root README](https://github.com/arturogarrido/claudinho#competitions)), or an ESPN slug such as `eng.1` (any other lower-case dotted ESPN slug works too, labelled experimental). Also `CLAUDINHO_COMPETITION` (an alias or a slug), which the statusline, the hook and `ambient --json` follow, and the choice `claudinho follow <alias>` saves: the flag, then the environment, then the saved choice; with none, a command says so (exit 1; `--json` prints `{ "competition": null, "noCompetition": true }`), the statusline reads `⚽ claudinho follow` (`ambient --json` prints it as `line` beside `competition: null`), and the hook stays silent. Every answer names its competition on a line after its header (`Premier League · from the command line`; first where an answer has none; `table` once, before its tables) and in `--json` as `competition`; `share` prints no such line: its card's title names the competition and its run cue selects it; `team` names the World Cup roster it reads. An unknown value is refused with the list. |
|
|
113
115
|
| `--source <name>` | live data provider (advanced; sensible default) |
|
|
114
116
|
| `--flavor <level>` | commentary flair: `off`, `subtle`, `full` (default: `full`; also `CLAUDINHO_FLAVOR`) |
|
|
115
117
|
| `--no-markets` | hide prediction-market signals in `today`/`match` (also `CLAUDINHO_MARKETS=off`) |
|
|
@@ -271,6 +273,24 @@ Wires `claudinho hook` into Claude Code's `UserPromptSubmit`. During a match,
|
|
|
271
273
|
the live score is injected into Claude's context so it can mention it naturally;
|
|
272
274
|
off-match it's silent (zero added tokens). Restart Claude Code to activate.
|
|
273
275
|
|
|
276
|
+
**Moving to the Claude Code plugin?** `claudinho init plugin` removes the statusline
|
|
277
|
+
(`claudinho prompt`) and the live-score hook (`claudinho hook`) that `init claude` wrote to
|
|
278
|
+
`~/.claude/settings.json`, keeps every other setting, then prints the plugin's install line and the
|
|
279
|
+
MCP one-liner; an edited command (a wrapper, `npx -y @claudinho/cli hook`) stays, and it names it.
|
|
280
|
+
The plugin itself (the band above the prompt, the score toasts, the prompt context) installs from the
|
|
281
|
+
repository: see the [Claude Code plugin](https://github.com/arturogarrido/claudinho#claude-code-plugin) section.
|
|
282
|
+
|
|
283
|
+
### For a program: `claudinho ambient --json`
|
|
284
|
+
|
|
285
|
+
`claudinho ambient --json [--columns N]` prints the statusline and the hook as one JSON object on
|
|
286
|
+
one line, for a program that draws them itself: `line` (what `claudinho prompt` prints, fitted to N
|
|
287
|
+
columns with the `+N` count kept; N a positive integer), `context` (the hook's block, or `null`),
|
|
288
|
+
`live` (the live matches, each marked `picked` and `pinned`), `current` (an empty list means nothing
|
|
289
|
+
is on only when this is true), `next`, `pick`, `competition`, `degraded`, `source`, `updatedAt`,
|
|
290
|
+
`staleAfter` and `disclaimer` (the non-affiliation sentence). Beside `line` the view says what the line is, `idle` (the edition-complete line, however narrow it was fitted) and `empty` (nothing known), read before the fit so a program never reads a pace or a visibility off the text; and each live record carries `line`, the hook's own line for it with its flags and roster names, which a program says and never composes. It reads only the local cache, never
|
|
291
|
+
the network; the command itself exits 0 whatever the cache holds, and any other `--columns` value is
|
|
292
|
+
refused by the option parser, as a wrong option is for every command.
|
|
293
|
+
|
|
274
294
|
## Statusline (Cursor CLI)
|
|
275
295
|
|
|
276
296
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -762,7 +762,7 @@ function formatTime(iso, opts = {}) {
|
|
|
762
762
|
}).format(when);
|
|
763
763
|
}
|
|
764
764
|
var COUNTDOWN_NOW = "now";
|
|
765
|
-
function countdown(iso, from
|
|
765
|
+
function countdown(iso, from) {
|
|
766
766
|
const when = parsedDate(iso);
|
|
767
767
|
if (!when) return UNKNOWN_TIME;
|
|
768
768
|
const ms = when.getTime() - from.getTime();
|
|
@@ -775,7 +775,7 @@ function countdown(iso, from = /* @__PURE__ */ new Date()) {
|
|
|
775
775
|
if (hours > 0) return `${hours}h${mins}m`;
|
|
776
776
|
return `${mins}m`;
|
|
777
777
|
}
|
|
778
|
-
function countdownPhrase(lang, iso, from
|
|
778
|
+
function countdownPhrase(lang, iso, from) {
|
|
779
779
|
const left = countdown(iso, from);
|
|
780
780
|
return left === COUNTDOWN_NOW ? t(lang, "countdown.now") : t(lang, "next.in", { countdown: left });
|
|
781
781
|
}
|
|
@@ -4745,7 +4745,7 @@ function fetchMeta(result) {
|
|
|
4745
4745
|
}
|
|
4746
4746
|
var ESPN_SOCCER = "https://site.api.espn.com/apis/site/v2/sports/soccer";
|
|
4747
4747
|
var DEFAULT_COMPETITION = BUNDLED_SLUG;
|
|
4748
|
-
var USER_AGENT = `claudinho/${"0.11.
|
|
4748
|
+
var USER_AGENT = `claudinho/${"0.11.2"} (+https://github.com/arturogarrido/claudinho)`;
|
|
4749
4749
|
var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
4750
4750
|
var DEFAULT_COOLDOWN_MS = 5 * 6e4;
|
|
4751
4751
|
var MAX_COOLDOWN_MS = 15 * 6e4;
|
|
@@ -6237,6 +6237,9 @@ function mergeLive(base, live) {
|
|
|
6237
6237
|
function clockOf(adapter) {
|
|
6238
6238
|
return new Date(adapter.now?.() ?? Date.now());
|
|
6239
6239
|
}
|
|
6240
|
+
function requestClock(adapter, given) {
|
|
6241
|
+
return given ?? clockOf(adapter);
|
|
6242
|
+
}
|
|
6240
6243
|
function providerDayOf(adapter, instant) {
|
|
6241
6244
|
if (Number.isNaN(instant.getTime())) return "";
|
|
6242
6245
|
return adapter.bucketDay?.(instant) || instant.toISOString().slice(0, 10);
|
|
@@ -6411,7 +6414,7 @@ async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Dat
|
|
|
6411
6414
|
});
|
|
6412
6415
|
let refreshRead = {};
|
|
6413
6416
|
if (candidate) {
|
|
6414
|
-
const { partial, served: _served, ...r } = await getMatchById(adapter, candidate.id);
|
|
6417
|
+
const { partial, served: _served, ...r } = await getMatchById(adapter, candidate.id, now);
|
|
6415
6418
|
refreshRead = partial ? { partial } : {};
|
|
6416
6419
|
const m = r.degraded ? candidate : r.match ?? candidate;
|
|
6417
6420
|
if (!isFinished(m.status)) return { ...r, match: m, ...bothReads(windowRead, refreshRead) };
|
|
@@ -8730,6 +8733,14 @@ function makeT(lang) {
|
|
|
8730
8733
|
};
|
|
8731
8734
|
}
|
|
8732
8735
|
|
|
8736
|
+
// src/options.ts
|
|
8737
|
+
import { InvalidArgumentError } from "commander";
|
|
8738
|
+
function positiveInteger(value) {
|
|
8739
|
+
const n = /^\d+$/.test(value) ? Number.parseInt(value, 10) : 0;
|
|
8740
|
+
if (!(n > 0)) throw new InvalidArgumentError("a positive integer");
|
|
8741
|
+
return n;
|
|
8742
|
+
}
|
|
8743
|
+
|
|
8733
8744
|
// src/tagline.ts
|
|
8734
8745
|
var NAMED = ["the World Cup", "the Premier League", "LALIGA", "the Champions League"];
|
|
8735
8746
|
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.`;
|
|
@@ -8969,7 +8980,9 @@ function readState(source, competition) {
|
|
|
8969
8980
|
const bytes = readSmallFile(cachePath(source, competition), MAX_STATE_BYTES);
|
|
8970
8981
|
if (!bytes) return void 0;
|
|
8971
8982
|
const parsed = JSON.parse(bytes.toString("utf8"));
|
|
8972
|
-
|
|
8983
|
+
if (!isCacheState(parsed)) return void 0;
|
|
8984
|
+
if (parsed.liveComplete !== void 0 && typeof parsed.liveComplete !== "boolean") delete parsed.liveComplete;
|
|
8985
|
+
return parsed;
|
|
8973
8986
|
} catch {
|
|
8974
8987
|
return void 0;
|
|
8975
8988
|
}
|
|
@@ -9448,6 +9461,7 @@ function applyDiscovery(prev, answer, now) {
|
|
|
9448
9461
|
// src/statusline.ts
|
|
9449
9462
|
var DISPLAY_STALE_MS = 5 * 6e4;
|
|
9450
9463
|
var TOURNAMENT_COMPLETE_LINE = "\u26BD World Cup 2026 is complete \xB7 claudinho follow --list";
|
|
9464
|
+
var NOTHING_KNOWN_LINE = "\u26BD \u2014";
|
|
9451
9465
|
var FIRST_RUN_LINE = "\u26BD claudinho follow";
|
|
9452
9466
|
function pickAmbientMatch(matches, pick2) {
|
|
9453
9467
|
if (!pick2) return [...matches];
|
|
@@ -9577,7 +9591,11 @@ function liveMatchesFromCache(state, nowMs, kind) {
|
|
|
9577
9591
|
var DEFAULT_MAX_SEGMENTS = 8;
|
|
9578
9592
|
var MAX_LINE_COLUMNS = 200;
|
|
9579
9593
|
function renderPrompt(state, opts = {}) {
|
|
9580
|
-
return
|
|
9594
|
+
return promptLine(state, opts).line;
|
|
9595
|
+
}
|
|
9596
|
+
function promptLine(state, opts = {}) {
|
|
9597
|
+
const unfitted = renderPromptLine(state, opts);
|
|
9598
|
+
return { line: truncateVisible(unfitted, lineColumns(opts)), unfitted };
|
|
9581
9599
|
}
|
|
9582
9600
|
function renderPromptLine(state, opts = {}) {
|
|
9583
9601
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
@@ -9589,52 +9607,77 @@ function renderPromptLine(state, opts = {}) {
|
|
|
9589
9607
|
const kind = opts.teamKind ?? defaultTeamKind(defaultCompetition);
|
|
9590
9608
|
const liveList = liveMatchesFromCache(state, nowMs, kind);
|
|
9591
9609
|
const live = liveList.items;
|
|
9592
|
-
const
|
|
9593
|
-
const
|
|
9594
|
-
const bundle = defaultCompetition ? allFixtures() : [];
|
|
9595
|
-
const schedule = cachedFixtures.length ? mergeLive(bundle, cachedFixtures) : defaultCompetition ? void 0 : [];
|
|
9610
|
+
const { cachedFixtures, schedule } = countdownSchedule(state, defaultCompetition, kind);
|
|
9611
|
+
const width = lineColumns(opts);
|
|
9596
9612
|
if (live.length > 0) {
|
|
9597
9613
|
const ordered = pickAmbientMatch(live, pick2);
|
|
9598
9614
|
const picked = ordered[0] !== void 0 && isPicked(ordered[0], pick2);
|
|
9599
9615
|
const max = picked ? 1 : opts.max && opts.max > 0 ? Math.min(opts.max, DEFAULT_MAX_SEGMENTS) : DEFAULT_MAX_SEGMENTS;
|
|
9600
9616
|
const shown = ordered.slice(0, max);
|
|
9601
9617
|
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;
|
|
9618
|
+
return fitLiveLine(shown.map((m) => matchSegment(m, compact, flags)), overflow, liveList.complete, width);
|
|
9605
9619
|
}
|
|
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);
|
|
9620
|
+
const win = syncingWindow(state, nowMs, liveWhole(liveList.complete, state), defaultCompetition, cachedFixtures, schedule, pick2);
|
|
9621
|
+
if (win) {
|
|
9623
9622
|
const first = win[0];
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
return `\u26BD ${matchup}live \xB7 syncing\u2026` + (more > 0 ? ` +${more}` : "");
|
|
9628
|
-
}
|
|
9623
|
+
const matchup = first && isResolvedFixture(first) ? `${teamTok(first.home, flags)} vs ${teamTok(first.away, flags)} ` : "";
|
|
9624
|
+
const more = win.length - 1;
|
|
9625
|
+
return fitWithMarker(`\u26BD ${matchup}live \xB7 syncing\u2026`, more > 0 ? ` +${more}` : "", width);
|
|
9629
9626
|
}
|
|
9630
|
-
const next =
|
|
9627
|
+
const next = countdownFixture(nowMs, schedule, pick2);
|
|
9631
9628
|
if (next) {
|
|
9632
9629
|
return `${teamTok(next.home, flags)} vs ${teamTok(next.away, flags)} in ${countdown(next.kickoff, now)}`;
|
|
9633
9630
|
}
|
|
9634
9631
|
if (defaultCompetition && isTournamentWindowOver(nowMs, schedule)) {
|
|
9635
9632
|
return TOURNAMENT_COMPLETE_LINE;
|
|
9636
9633
|
}
|
|
9637
|
-
return
|
|
9634
|
+
return NOTHING_KNOWN_LINE;
|
|
9635
|
+
}
|
|
9636
|
+
function lineColumns(opts) {
|
|
9637
|
+
const c = opts.columns;
|
|
9638
|
+
return typeof c === "number" && c >= 1 ? Math.min(Math.floor(c), MAX_LINE_COLUMNS) : MAX_LINE_COLUMNS;
|
|
9639
|
+
}
|
|
9640
|
+
function fitWithMarker(body, marker, width) {
|
|
9641
|
+
const markerWidth = displayWidth(marker);
|
|
9642
|
+
if (markerWidth > width) return truncateVisible(marker, width);
|
|
9643
|
+
const room = width - markerWidth;
|
|
9644
|
+
return (room < 1 ? "" : truncateVisible(body, room)) + marker;
|
|
9645
|
+
}
|
|
9646
|
+
function fitLiveLine(segments, overflow, complete, width) {
|
|
9647
|
+
const markerFor = (dropped) => !complete ? " +more" : overflow + dropped > 0 ? ` +${overflow + dropped}` : "";
|
|
9648
|
+
const bodyOf = (kept2) => "\u26BD " + segments.slice(0, kept2).join(" \xB7 ");
|
|
9649
|
+
let kept = segments.length;
|
|
9650
|
+
while (kept > 1 && displayWidth(bodyOf(kept)) + displayWidth(markerFor(segments.length - kept)) > width) kept--;
|
|
9651
|
+
return fitWithMarker(bodyOf(kept), markerFor(segments.length - kept), width);
|
|
9652
|
+
}
|
|
9653
|
+
function countdownSchedule(state, defaultCompetition, kind) {
|
|
9654
|
+
const cachedFixtureList = sealFixtures(defaultCompetition ? state?.fixtures : state?.schedule?.fixtures, kind);
|
|
9655
|
+
const cachedFixtures = defaultCompetition ? [...cachedFixtureList.items] : cachedFixtureList.items.filter((m) => hasLiveWindow(m.status));
|
|
9656
|
+
const bundle = defaultCompetition ? allFixtures() : [];
|
|
9657
|
+
const schedule = cachedFixtures.length ? mergeLive(bundle, cachedFixtures) : defaultCompetition ? void 0 : [];
|
|
9658
|
+
return { cachedFixtures, schedule };
|
|
9659
|
+
}
|
|
9660
|
+
function liveWhole(scanComplete, state) {
|
|
9661
|
+
return scanComplete && state?.liveComplete !== false;
|
|
9662
|
+
}
|
|
9663
|
+
function syncingWindow(state, nowMs, liveComplete, defaultCompetition, cachedFixtures, schedule, pick2) {
|
|
9664
|
+
const cacheFresh = !!state && state.degraded !== true && ageMs(state, nowMs) < DISPLAY_STALE_MS;
|
|
9665
|
+
if (cacheFresh && liveComplete) return void 0;
|
|
9666
|
+
if (!defaultCompetition) {
|
|
9667
|
+
if (!scheduleGateOpen(scheduleView(state?.schedule, nowMs), nowMs, { probe: false })) return void 0;
|
|
9668
|
+
return pickAmbientMatch(
|
|
9669
|
+
cachedFixtures.filter((m) => {
|
|
9670
|
+
const k = Date.parse(m.kickoff);
|
|
9671
|
+
return k <= nowMs && nowMs < k + LIVE_WINDOW_MS;
|
|
9672
|
+
}).sort(byKickoff),
|
|
9673
|
+
pick2
|
|
9674
|
+
);
|
|
9675
|
+
}
|
|
9676
|
+
const win = pickAmbientMatch(fixturesInLiveWindow(nowMs, schedule), pick2);
|
|
9677
|
+
return win.length > 0 ? win : void 0;
|
|
9678
|
+
}
|
|
9679
|
+
function countdownFixture(nowMs, schedule, pick2) {
|
|
9680
|
+
return pickAmbientMatch(upcomingResolved(nowMs, schedule), pick2)[0];
|
|
9638
9681
|
}
|
|
9639
9682
|
|
|
9640
9683
|
// src/hook.ts
|
|
@@ -9652,7 +9695,8 @@ function rosterPinned(t2, pin) {
|
|
|
9652
9695
|
const { team } = lookupTeam(t2.code);
|
|
9653
9696
|
return team ? { ...t2, name: team.name, flag: team.flag } : t2;
|
|
9654
9697
|
}
|
|
9655
|
-
function
|
|
9698
|
+
function hookLine(m, opts) {
|
|
9699
|
+
const { flags, pin } = opts;
|
|
9656
9700
|
const minute = m.status === "HT" ? "half-time" : m.minute ? `${m.minute}'` : "live";
|
|
9657
9701
|
const h = rosterPinned(m.home, pin);
|
|
9658
9702
|
const a = rosterPinned(m.away, pin);
|
|
@@ -9670,13 +9714,51 @@ function renderHook(state, opts = {}) {
|
|
|
9670
9714
|
const live = pickAmbientMatch(liveList.items, opts.pick);
|
|
9671
9715
|
const shown = live.slice(0, MAX_HOOK_MATCHES);
|
|
9672
9716
|
const overflow = live.length - shown.length;
|
|
9673
|
-
const lines = shown.map((mm) =>
|
|
9717
|
+
const lines = shown.map((mm) => hookLine(mm, { flags, pin })).join("\n");
|
|
9674
9718
|
const more = !liveList.complete ? "\n(more live matches may not be shown)" : overflow > 0 ? `
|
|
9675
9719
|
(+${overflow} more not shown)` : "";
|
|
9676
9720
|
return boundContext(`[Claudinho \u2014 live football scores right now]
|
|
9677
9721
|
${lines}`, more);
|
|
9678
9722
|
}
|
|
9679
9723
|
|
|
9724
|
+
// src/ambient.ts
|
|
9725
|
+
function ambientView(state, opts = {}) {
|
|
9726
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
9727
|
+
const nowMs = now.getTime();
|
|
9728
|
+
const defaultCompetition = opts.defaultCompetition ?? true;
|
|
9729
|
+
const kind = opts.teamKind ?? defaultTeamKind(opts.defaultCompetition);
|
|
9730
|
+
const pin = opts.pin ?? (opts.pick && "team" in opts.pick ? opts.pick.team : void 0);
|
|
9731
|
+
const read = { ...opts, now };
|
|
9732
|
+
const hookOpts = { flags: opts.flags ?? true, pin: defaultCompetition };
|
|
9733
|
+
const list = liveMatchesFromCache(state, nowMs, kind);
|
|
9734
|
+
const items = pickAmbientMatch(list.items, opts.pick).map((m) => ({
|
|
9735
|
+
...m,
|
|
9736
|
+
picked: isPicked(m, opts.pick),
|
|
9737
|
+
pinned: pin !== void 0 && (isPinnedSide(m.home, pin) || isPinnedSide(m.away, pin)),
|
|
9738
|
+
line: hookLine(m, hookOpts)
|
|
9739
|
+
}));
|
|
9740
|
+
const prompt = promptLine(state, read);
|
|
9741
|
+
const context = renderHook(state, read);
|
|
9742
|
+
const { cachedFixtures, schedule } = countdownSchedule(state, defaultCompetition, kind);
|
|
9743
|
+
const age = stampAgeMs(state?.updatedAt, nowMs);
|
|
9744
|
+
const stamp = state && Number.isFinite(age) ? Date.parse(state.updatedAt) : void 0;
|
|
9745
|
+
const staleAt = stamp === void 0 ? void 0 : new Date(stamp + DISPLAY_STALE_MS);
|
|
9746
|
+
const syncing = list.items.length === 0 && syncingWindow(state, nowMs, liveWhole(list.complete, state), defaultCompetition, cachedFixtures, schedule, opts.pick) !== void 0;
|
|
9747
|
+
return {
|
|
9748
|
+
line: prompt.line,
|
|
9749
|
+
idle: prompt.unfitted === TOURNAMENT_COMPLETE_LINE,
|
|
9750
|
+
empty: prompt.unfitted === NOTHING_KNOWN_LINE,
|
|
9751
|
+
context: context === "" ? null : context,
|
|
9752
|
+
live: { items, total: list.total, shown: list.shown, truncated: list.truncated, complete: list.complete },
|
|
9753
|
+
current: stamp !== void 0 && age < DISPLAY_STALE_MS && state?.degraded !== true && state?.liveComplete === true && !syncing,
|
|
9754
|
+
next: countdownFixture(nowMs, schedule, opts.pick) ?? null,
|
|
9755
|
+
degraded: state?.degraded === true,
|
|
9756
|
+
source: typeof state?.source === "string" ? state.source : null,
|
|
9757
|
+
updatedAt: stamp === void 0 ? null : new Date(stamp).toISOString(),
|
|
9758
|
+
staleAfter: staleAt === void 0 || Number.isNaN(staleAt.getTime()) ? null : staleAt.toISOString()
|
|
9759
|
+
};
|
|
9760
|
+
}
|
|
9761
|
+
|
|
9680
9762
|
// src/refresh.ts
|
|
9681
9763
|
import { spawn } from "child_process";
|
|
9682
9764
|
var MIN_REFRESH_MS = 12e3;
|
|
@@ -9721,6 +9803,8 @@ async function runRefresh(opts) {
|
|
|
9721
9803
|
{
|
|
9722
9804
|
updatedAt: now.toISOString(),
|
|
9723
9805
|
live: [],
|
|
9806
|
+
liveComplete: false,
|
|
9807
|
+
// nothing was read
|
|
9724
9808
|
degraded: true,
|
|
9725
9809
|
// no live provider served this scope — never claim otherwise
|
|
9726
9810
|
source,
|
|
@@ -9754,6 +9838,8 @@ async function runRefresh(opts) {
|
|
|
9754
9838
|
{
|
|
9755
9839
|
updatedAt: now.toISOString(),
|
|
9756
9840
|
live: [],
|
|
9841
|
+
liveComplete: false,
|
|
9842
|
+
// nothing was read
|
|
9757
9843
|
degraded: until !== void 0,
|
|
9758
9844
|
source,
|
|
9759
9845
|
competition,
|
|
@@ -9796,6 +9882,7 @@ async function runRefresh(opts) {
|
|
|
9796
9882
|
const { needLive, needFixtures } = plan(base);
|
|
9797
9883
|
if (!needLive && !needFixtures) return;
|
|
9798
9884
|
let live = base?.live ?? [];
|
|
9885
|
+
let liveComplete = base ? base.liveComplete : false;
|
|
9799
9886
|
let degraded = base?.degraded ?? false;
|
|
9800
9887
|
let updatedAt = base?.updatedAt ?? now.toISOString();
|
|
9801
9888
|
let fixtures = base?.fixtures;
|
|
@@ -9808,11 +9895,13 @@ async function runRefresh(opts) {
|
|
|
9808
9895
|
const adapter = liveAdapter(source, competition, clock);
|
|
9809
9896
|
if (needLive) {
|
|
9810
9897
|
try {
|
|
9811
|
-
const r = await
|
|
9898
|
+
const r = await getLiveRead(adapter, now);
|
|
9812
9899
|
live = r.matches;
|
|
9900
|
+
liveComplete = r.complete === true;
|
|
9813
9901
|
degraded = r.degraded;
|
|
9814
9902
|
if (!r.degraded) season = r.season;
|
|
9815
9903
|
} catch {
|
|
9904
|
+
liveComplete = false;
|
|
9816
9905
|
degraded = true;
|
|
9817
9906
|
}
|
|
9818
9907
|
updatedAt = now.toISOString();
|
|
@@ -9847,6 +9936,7 @@ async function runRefresh(opts) {
|
|
|
9847
9936
|
{
|
|
9848
9937
|
updatedAt,
|
|
9849
9938
|
live,
|
|
9939
|
+
...liveComplete !== void 0 ? { liveComplete } : {},
|
|
9850
9940
|
degraded,
|
|
9851
9941
|
source,
|
|
9852
9942
|
competition,
|
|
@@ -9935,6 +10025,7 @@ async function refreshOffBundle(c) {
|
|
|
9935
10025
|
const clock = () => nowMs + (Date.now() - realStart);
|
|
9936
10026
|
const adapter = liveAdapter(source, competition, clock);
|
|
9937
10027
|
let live = base?.live ?? [];
|
|
10028
|
+
let liveComplete = base ? base.liveComplete : false;
|
|
9938
10029
|
let degraded = base?.degraded ?? false;
|
|
9939
10030
|
let updatedAt = base?.updatedAt ?? NEVER;
|
|
9940
10031
|
let season = sealSeason(base?.season);
|
|
@@ -9951,6 +10042,7 @@ async function refreshOffBundle(c) {
|
|
|
9951
10042
|
const snapshot = (backoffUntil2) => ({
|
|
9952
10043
|
updatedAt,
|
|
9953
10044
|
live,
|
|
10045
|
+
...liveComplete !== void 0 ? { liveComplete } : {},
|
|
9954
10046
|
degraded,
|
|
9955
10047
|
source,
|
|
9956
10048
|
competition,
|
|
@@ -10006,11 +10098,13 @@ async function refreshOffBundle(c) {
|
|
|
10006
10098
|
try {
|
|
10007
10099
|
const r = await getLiveRead(adapter, now);
|
|
10008
10100
|
live = r.matches;
|
|
10101
|
+
liveComplete = r.complete === true;
|
|
10009
10102
|
degraded = r.degraded;
|
|
10010
10103
|
if (!r.degraded) season = r.season;
|
|
10011
10104
|
if (r.matches.length > 0) inPlayUntil = raiseInPlay(inPlayUntil, r.matches, nowMs);
|
|
10012
10105
|
else if (!r.degraded && r.complete) inPlayUntil = void 0;
|
|
10013
10106
|
} catch {
|
|
10107
|
+
liveComplete = false;
|
|
10014
10108
|
degraded = true;
|
|
10015
10109
|
}
|
|
10016
10110
|
updatedAt = new Date(liveAt).toISOString();
|
|
@@ -10127,6 +10221,9 @@ function manual(path, snippet, why) {
|
|
|
10127
10221
|
${snippet}` };
|
|
10128
10222
|
}
|
|
10129
10223
|
function readSettings(path, snippet) {
|
|
10224
|
+
return readSettingsOr(path, (why) => manual(path, snippet, why));
|
|
10225
|
+
}
|
|
10226
|
+
function readSettingsOr(path, refuse) {
|
|
10130
10227
|
let entry;
|
|
10131
10228
|
try {
|
|
10132
10229
|
entry = lstatSync3(path);
|
|
@@ -10134,15 +10231,15 @@ function readSettings(path, snippet) {
|
|
|
10134
10231
|
return {};
|
|
10135
10232
|
}
|
|
10136
10233
|
if (entry.isSymbolicLink() && !existsSync(path)) {
|
|
10137
|
-
return
|
|
10234
|
+
return refuse("Settings path is a symlink to a missing file:");
|
|
10138
10235
|
}
|
|
10139
10236
|
let parsed;
|
|
10140
10237
|
try {
|
|
10141
10238
|
parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
10142
10239
|
} catch {
|
|
10143
|
-
return
|
|
10240
|
+
return refuse("Could not parse");
|
|
10144
10241
|
}
|
|
10145
|
-
if (!isSettingsObject(parsed)) return
|
|
10242
|
+
if (!isSettingsObject(parsed)) return refuse("Not a JSON settings object:");
|
|
10146
10243
|
return parsed;
|
|
10147
10244
|
}
|
|
10148
10245
|
function isInitResult(v) {
|
|
@@ -10233,6 +10330,86 @@ function initHook(opts = {}) {
|
|
|
10233
10330
|
message: `Live-score hook configured in ${path}. Restart Claude Code; during matches, the score is injected into context on each prompt.`
|
|
10234
10331
|
};
|
|
10235
10332
|
}
|
|
10333
|
+
var PLUGIN_INSTALL_LINE = "/plugin install claudinho --marketplace arturogarrido/claudinho";
|
|
10334
|
+
var CLAUDE_MCP_ONELINER = "claude mcp add claudinho -- npx -y @claudinho/mcp";
|
|
10335
|
+
var PLUGIN_NEXT = `Install the plugin (in Claude Code), and the MCP tools (in a shell):
|
|
10336
|
+
${PLUGIN_INSTALL_LINE}
|
|
10337
|
+
${CLAUDE_MCP_ONELINER}`;
|
|
10338
|
+
var RUNS_CLAUDINHO = /claudinho/i;
|
|
10339
|
+
function initPlugin(opts = {}) {
|
|
10340
|
+
const path = opts.path ?? claudeSettingsPath();
|
|
10341
|
+
const refuse = (why) => ({
|
|
10342
|
+
action: "manual",
|
|
10343
|
+
path,
|
|
10344
|
+
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.
|
|
10345
|
+
${PLUGIN_NEXT}`
|
|
10346
|
+
});
|
|
10347
|
+
const parsed = readSettingsOr(path, refuse);
|
|
10348
|
+
if (isInitResult(parsed)) return parsed;
|
|
10349
|
+
const settings = parsed;
|
|
10350
|
+
if (settings.hooks !== void 0 && !isSettingsObject(settings.hooks)) {
|
|
10351
|
+
return refuse('Unexpected "hooks" shape in');
|
|
10352
|
+
}
|
|
10353
|
+
const hooks = settings.hooks;
|
|
10354
|
+
const eventSlot = hooks?.[CLAUDE_HOOK_EVENT];
|
|
10355
|
+
if (eventSlot !== void 0 && (!Array.isArray(eventSlot) || !validHookMatchers(eventSlot))) {
|
|
10356
|
+
return refuse(`Unexpected "hooks.${CLAUDE_HOOK_EVENT}" shape in`);
|
|
10357
|
+
}
|
|
10358
|
+
const left = /* @__PURE__ */ new Set();
|
|
10359
|
+
const removed = [];
|
|
10360
|
+
const statusLine = settings.statusLine;
|
|
10361
|
+
if (isSettingsObject(statusLine) && typeof statusLine.command === "string") {
|
|
10362
|
+
if (statusLine.command === DEFAULT_PROMPT_COMMAND) {
|
|
10363
|
+
delete settings.statusLine;
|
|
10364
|
+
removed.push("the statusline");
|
|
10365
|
+
} else if (RUNS_CLAUDINHO.test(statusLine.command)) {
|
|
10366
|
+
left.add(statusLine.command);
|
|
10367
|
+
}
|
|
10368
|
+
}
|
|
10369
|
+
if (hooks && Array.isArray(eventSlot)) {
|
|
10370
|
+
let dropped = 0;
|
|
10371
|
+
const matchers = [];
|
|
10372
|
+
for (const matcher of eventSlot) {
|
|
10373
|
+
const commands = matcher.hooks;
|
|
10374
|
+
if (!commands) {
|
|
10375
|
+
matchers.push(matcher);
|
|
10376
|
+
continue;
|
|
10377
|
+
}
|
|
10378
|
+
const kept = commands.filter((h) => h.command !== HOOK_COMMAND);
|
|
10379
|
+
for (const h of kept) if (typeof h.command === "string" && RUNS_CLAUDINHO.test(h.command)) left.add(h.command);
|
|
10380
|
+
dropped += commands.length - kept.length;
|
|
10381
|
+
if (kept.length === commands.length) matchers.push(matcher);
|
|
10382
|
+
else if (kept.length > 0) matchers.push({ ...matcher, hooks: kept });
|
|
10383
|
+
}
|
|
10384
|
+
if (dropped > 0) {
|
|
10385
|
+
removed.push("the live-score hook");
|
|
10386
|
+
if (matchers.length > 0) hooks[CLAUDE_HOOK_EVENT] = matchers;
|
|
10387
|
+
else {
|
|
10388
|
+
delete hooks[CLAUDE_HOOK_EVENT];
|
|
10389
|
+
if (Object.keys(hooks).length === 0) delete settings.hooks;
|
|
10390
|
+
}
|
|
10391
|
+
}
|
|
10392
|
+
}
|
|
10393
|
+
const named = left.size ? `
|
|
10394
|
+
Left in place (not the commands init claude writes; remove them yourself if the plugin replaces them):
|
|
10395
|
+
${[...left].map((c) => ` ${c}`).join("\n")}` : "";
|
|
10396
|
+
if (removed.length === 0) {
|
|
10397
|
+
return {
|
|
10398
|
+
action: "already",
|
|
10399
|
+
path,
|
|
10400
|
+
message: `Nothing init claude wrote is in ${path}; nothing was changed.${named}
|
|
10401
|
+
${PLUGIN_NEXT}`
|
|
10402
|
+
};
|
|
10403
|
+
}
|
|
10404
|
+
backupOnce(path);
|
|
10405
|
+
writeFileAtomic(path, JSON.stringify(settings, null, 2) + "\n", SETTINGS_WRITE);
|
|
10406
|
+
return {
|
|
10407
|
+
action: "written",
|
|
10408
|
+
path,
|
|
10409
|
+
message: `Removed ${removed.join(" and ")} that init claude wrote from ${path}.${named}
|
|
10410
|
+
${PLUGIN_NEXT}`
|
|
10411
|
+
};
|
|
10412
|
+
}
|
|
10236
10413
|
|
|
10237
10414
|
// src/providerBackoff.ts
|
|
10238
10415
|
function persistBackoff(source, competition, until, nowMs) {
|
|
@@ -10357,8 +10534,8 @@ async function reliableMarketSignalFor(ctx, match) {
|
|
|
10357
10534
|
const result = await reliableMarketSignals(ctx, [match]);
|
|
10358
10535
|
return { signal: result.signals.get(match.id), complete: result.complete };
|
|
10359
10536
|
}
|
|
10360
|
-
function out(
|
|
10361
|
-
process.stdout.write(
|
|
10537
|
+
function out(line = "") {
|
|
10538
|
+
process.stdout.write(line + "\n");
|
|
10362
10539
|
}
|
|
10363
10540
|
function emitJson(data) {
|
|
10364
10541
|
out(JSON.stringify(data, null, 2));
|
|
@@ -10443,13 +10620,16 @@ async function nextAsked(ctx, team, usage) {
|
|
|
10443
10620
|
const { cfg, t: t2 } = ctx;
|
|
10444
10621
|
const asked = teamAsked(team, cfg);
|
|
10445
10622
|
if (!asked) throw new InputError(usage);
|
|
10446
|
-
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
10447
10623
|
if ("pin" in asked) {
|
|
10448
10624
|
const code2 = bundleApplies(cfg.competition) ? asked.pin.code : asked.pin.name;
|
|
10449
|
-
|
|
10625
|
+
const adapter2 = adapterFor(ctx);
|
|
10626
|
+
const now2 = requestClock(adapter2, ctx.now);
|
|
10627
|
+
return { code: code2, next: await nextFixtureForPin(adapter2, asked.pin, now2), now: now2 };
|
|
10450
10628
|
}
|
|
10451
10629
|
const code = teamQuery(asked.query, usage, t2, cfg.competition);
|
|
10452
|
-
|
|
10630
|
+
const adapter = adapterFor(ctx);
|
|
10631
|
+
const now = requestClock(adapter, ctx.now);
|
|
10632
|
+
return { code, next: await getNextFixtureForTeam(adapter, code, now), now };
|
|
10453
10633
|
}
|
|
10454
10634
|
function nextEmptySentence(next, code, label2, cfg, t2) {
|
|
10455
10635
|
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,12 +10643,23 @@ function resolveEnvTeam(raw, competition) {
|
|
|
10463
10643
|
return /^[A-Za-z]{3}$/.test(raw) ? raw.toUpperCase() : void 0;
|
|
10464
10644
|
}
|
|
10465
10645
|
function ambientPick(cfg) {
|
|
10646
|
+
return ambientPreference(cfg).pick;
|
|
10647
|
+
}
|
|
10648
|
+
function ambientPreference(cfg) {
|
|
10466
10649
|
const raw = process.env.CLAUDINHO_TEAM;
|
|
10467
10650
|
if (raw) {
|
|
10468
10651
|
const code = resolveEnvTeam(raw, cfg.competition);
|
|
10469
|
-
return code ? { code } : void 0;
|
|
10652
|
+
return code ? { pick: { code }, unreadable: false } : { pick: void 0, unreadable: true };
|
|
10470
10653
|
}
|
|
10471
|
-
return cfg.pin ? { team: cfg.pin } : void 0;
|
|
10654
|
+
return { pick: cfg.pin ? { team: cfg.pin } : void 0, unreadable: false };
|
|
10655
|
+
}
|
|
10656
|
+
function promptLineEnv() {
|
|
10657
|
+
const compact = !["0", "false", "no"].includes(
|
|
10658
|
+
(process.env.CLAUDINHO_COMPACT ?? "").toLowerCase()
|
|
10659
|
+
);
|
|
10660
|
+
const maxRaw = Number.parseInt(process.env.CLAUDINHO_MAX ?? "", 10);
|
|
10661
|
+
const max = Number.isFinite(maxRaw) && maxRaw > 0 ? maxRaw : void 0;
|
|
10662
|
+
return { compact, max };
|
|
10472
10663
|
}
|
|
10473
10664
|
async function cmdToday(date, ctx) {
|
|
10474
10665
|
const { cfg, t: t2 } = ctx;
|
|
@@ -10569,7 +10760,7 @@ async function cmdLive(ctx) {
|
|
|
10569
10760
|
async function cmdNext(team, ctx) {
|
|
10570
10761
|
const { cfg, t: t2 } = ctx;
|
|
10571
10762
|
precheck(cfg, t2);
|
|
10572
|
-
const { code, next } = await nextAsked(ctx, team, teamUsage("next", cfg));
|
|
10763
|
+
const { code, next, now } = await nextAsked(ctx, team, teamUsage("next", cfg));
|
|
10573
10764
|
const { fixture, degraded, source } = next;
|
|
10574
10765
|
const label2 = next.team?.name ?? next.query ?? code;
|
|
10575
10766
|
if (cfg.json) {
|
|
@@ -10615,7 +10806,7 @@ async function cmdNext(team, ctx) {
|
|
|
10615
10806
|
out(
|
|
10616
10807
|
" " + c.dim(
|
|
10617
10808
|
joinSegments(
|
|
10618
|
-
isLive(fixture.status) ? [stage, when] : [stage, when, countdownPhrase(cfg.lang, fixture.kickoff)]
|
|
10809
|
+
isLive(fixture.status) ? [stage, when] : [stage, when, countdownPhrase(cfg.lang, fixture.kickoff, now)]
|
|
10619
10810
|
)
|
|
10620
10811
|
)
|
|
10621
10812
|
);
|
|
@@ -10822,11 +11013,7 @@ function cmdPrompt({ cfg, now }, io = {}) {
|
|
|
10822
11013
|
out(renderPromptOutput("\u26BD \u2014", payload));
|
|
10823
11014
|
return;
|
|
10824
11015
|
}
|
|
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;
|
|
11016
|
+
const { compact, max } = promptLineEnv();
|
|
10830
11017
|
const state = readCurrentState(cfg.source, cfg.competition);
|
|
10831
11018
|
const scoreLine = renderPrompt(state, {
|
|
10832
11019
|
// CLAUDINHO_TEAM by code (offline lookup — hot-path safe), else the saved pin.
|
|
@@ -10873,6 +11060,67 @@ function cmdHook({ cfg, now }) {
|
|
|
10873
11060
|
} catch {
|
|
10874
11061
|
}
|
|
10875
11062
|
}
|
|
11063
|
+
var AMBIENT_EMPTY = JSON.stringify({ line: NOTHING_KNOWN_LINE, empty: true });
|
|
11064
|
+
function cmdAmbient({ cfg, now }, opts = {}) {
|
|
11065
|
+
let printed = false;
|
|
11066
|
+
try {
|
|
11067
|
+
if (cfg.selection.kind === "none") {
|
|
11068
|
+
out(
|
|
11069
|
+
JSON.stringify({
|
|
11070
|
+
...selectionExtras(cfg.selection),
|
|
11071
|
+
...verdictExtras(selectionVerdict(cfg.selection)),
|
|
11072
|
+
line: FIRST_RUN_LINE
|
|
11073
|
+
})
|
|
11074
|
+
);
|
|
11075
|
+
return;
|
|
11076
|
+
}
|
|
11077
|
+
if (cfg.selection.kind !== "selected") {
|
|
11078
|
+
out(AMBIENT_EMPTY);
|
|
11079
|
+
return;
|
|
11080
|
+
}
|
|
11081
|
+
const { compact, max } = promptLineEnv();
|
|
11082
|
+
const preference = ambientPreference(cfg);
|
|
11083
|
+
const state = readCurrentState(cfg.source, cfg.competition);
|
|
11084
|
+
const read = ambientView(state, {
|
|
11085
|
+
pick: preference.pick,
|
|
11086
|
+
// The SAVED pin, for each record's `pinned` (whatever CLAUDINHO_TEAM picks).
|
|
11087
|
+
pin: cfg.pin,
|
|
11088
|
+
compact,
|
|
11089
|
+
max,
|
|
11090
|
+
flags: flagsEnabled(),
|
|
11091
|
+
defaultCompetition: bundleApplies(cfg.competition),
|
|
11092
|
+
teamKind: teamKind(cfg.competition),
|
|
11093
|
+
now,
|
|
11094
|
+
columns: opts.columns
|
|
11095
|
+
});
|
|
11096
|
+
const pick2 = preference.pick;
|
|
11097
|
+
const view = {
|
|
11098
|
+
line: read.line,
|
|
11099
|
+
idle: read.idle,
|
|
11100
|
+
empty: read.empty,
|
|
11101
|
+
context: read.context,
|
|
11102
|
+
live: read.live,
|
|
11103
|
+
current: read.current,
|
|
11104
|
+
next: read.next,
|
|
11105
|
+
pick: pick2 === void 0 ? null : "code" in pick2 ? { code: pick2.code } : pick2.team,
|
|
11106
|
+
...preference.unreadable ? { pickUnreadable: true } : {},
|
|
11107
|
+
// A selected competition: core's key is present (see `selectionExtras`).
|
|
11108
|
+
...selectionExtras(cfg.selection),
|
|
11109
|
+
degraded: read.degraded,
|
|
11110
|
+
source: read.source,
|
|
11111
|
+
updatedAt: read.updatedAt,
|
|
11112
|
+
staleAfter: read.staleAfter,
|
|
11113
|
+
disclaimer: DISCLAIMER
|
|
11114
|
+
};
|
|
11115
|
+
out(JSON.stringify(view));
|
|
11116
|
+
printed = true;
|
|
11117
|
+
if (refreshWanted(now?.getTime() ?? Date.now(), state, cfg.competition, cfg.source)) {
|
|
11118
|
+
spawnRefresh(cfg.source, cfg.competition);
|
|
11119
|
+
}
|
|
11120
|
+
} catch {
|
|
11121
|
+
if (!printed) out(AMBIENT_EMPTY);
|
|
11122
|
+
}
|
|
11123
|
+
}
|
|
10876
11124
|
async function cmdRefresh({ cfg }) {
|
|
10877
11125
|
if (cfg.selection.kind !== "selected") return;
|
|
10878
11126
|
await runRefresh({ source: cfg.source, competition: cfg.competition });
|
|
@@ -10911,7 +11159,6 @@ var CURSOR_MCP_SNIPPET = `{
|
|
|
10911
11159
|
"claudinho": { "command": "npx", "args": ["-y", "@claudinho/mcp"] }
|
|
10912
11160
|
}
|
|
10913
11161
|
}`;
|
|
10914
|
-
var CLAUDE_MCP_ONELINER = "claude mcp add claudinho -- npx -y @claudinho/mcp";
|
|
10915
11162
|
function cmdInitCursor(opts, { cfg }) {
|
|
10916
11163
|
if (opts.print) {
|
|
10917
11164
|
out("# 1) Cursor CLI statusline \u2192 ~/.cursor/cli-config.json");
|
|
@@ -10957,12 +11204,17 @@ function cmdInitClaude(opts, { cfg }) {
|
|
|
10957
11204
|
printInitChoose(cfg);
|
|
10958
11205
|
if (statusRes.action === "written" || hookRes.action === "written") printInitStarCta(cfg);
|
|
10959
11206
|
}
|
|
11207
|
+
function cmdInitPlugin({ cfg }) {
|
|
11208
|
+
printInitResult(initPlugin(), cfg);
|
|
11209
|
+
}
|
|
10960
11210
|
async function cmdMatch(id, ctx) {
|
|
10961
11211
|
const { cfg, t: t2 } = ctx;
|
|
10962
11212
|
precheck(cfg, t2);
|
|
10963
|
-
const
|
|
11213
|
+
const adapter = adapterFor(ctx);
|
|
11214
|
+
const now = requestClock(adapter, ctx.now);
|
|
11215
|
+
const found = await getMatchById(adapter, id, now);
|
|
10964
11216
|
const { match, degraded, source: liveSource } = found;
|
|
10965
|
-
const market = match ? await reliableMarketSignalFor(ctx, match) : { signal: void 0, complete: true };
|
|
11217
|
+
const market = match ? await reliableMarketSignalFor({ ...ctx, now }, match) : { signal: void 0, complete: true };
|
|
10966
11218
|
if (cfg.json) {
|
|
10967
11219
|
emitJson({
|
|
10968
11220
|
degraded,
|
|
@@ -11040,7 +11292,7 @@ function noSignalLine(m, now, competition) {
|
|
|
11040
11292
|
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
11293
|
}
|
|
11042
11294
|
function printMarketBlock(m, sig, c) {
|
|
11043
|
-
for (const
|
|
11295
|
+
for (const line of marketBlock(sig, m)) out(" " + c.dim(line));
|
|
11044
11296
|
}
|
|
11045
11297
|
async function cmdMarkets(target, team, ctx) {
|
|
11046
11298
|
const { cfg, t: t2 } = ctx;
|
|
@@ -11097,7 +11349,6 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
11097
11349
|
}
|
|
11098
11350
|
if (target && target !== "today" && !isValidDate(target)) {
|
|
11099
11351
|
precheck(cfg, t2);
|
|
11100
|
-
const now2 = ctx.now ?? /* @__PURE__ */ new Date();
|
|
11101
11352
|
const scope = marketScopeVerdict(cfg.competition, 0);
|
|
11102
11353
|
const outOfScope = verdictNotice(scope, cfg.lang);
|
|
11103
11354
|
if (outOfScope !== void 0) {
|
|
@@ -11121,7 +11372,9 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
11121
11372
|
out(c3.dim(MARKET_INFO));
|
|
11122
11373
|
return;
|
|
11123
11374
|
}
|
|
11124
|
-
const
|
|
11375
|
+
const adapter = adapterFor(ctx);
|
|
11376
|
+
const now2 = requestClock(adapter, ctx.now);
|
|
11377
|
+
const found = await getMatchById(adapter, target, now2);
|
|
11125
11378
|
const { match } = found;
|
|
11126
11379
|
const market = match && marketRelevant(match, now2) ? await marketSignalsFor(ctx, [match], MARKETS_CMD_OPTS) : { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
11127
11380
|
const sig = match ? market.signals.get(match.id) : void 0;
|
|
@@ -11356,15 +11609,17 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
11356
11609
|
}
|
|
11357
11610
|
if (target === "next") {
|
|
11358
11611
|
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] : []);
|
|
11612
|
+
const { code, next, now } = await nextAsked(ctx, team, teamUsage("share next", cfg));
|
|
11613
|
+
const market2 = await reliableShareSignals({ ...ctx, now }, next.fixture ? [next.fixture] : []);
|
|
11361
11614
|
emitMatchCard(ctx, nextShareCard(next, code, market2, where), baseOptions, copy);
|
|
11362
11615
|
return;
|
|
11363
11616
|
}
|
|
11364
11617
|
if (target && target !== "today" && !isValidDate(target)) {
|
|
11365
11618
|
precheck(cfg, t2);
|
|
11366
|
-
const
|
|
11367
|
-
const
|
|
11619
|
+
const adapter = adapterFor(ctx);
|
|
11620
|
+
const now = requestClock(adapter, ctx.now);
|
|
11621
|
+
const found = await getMatchById(adapter, target, now);
|
|
11622
|
+
const market2 = await reliableShareSignals({ ...ctx, now }, found.match ? [found.match] : []);
|
|
11368
11623
|
emitMatchCard(ctx, matchShareCard(found, target, market2, where), baseOptions, copy);
|
|
11369
11624
|
return;
|
|
11370
11625
|
}
|
|
@@ -11681,7 +11936,7 @@ function cmdVibe(ctx) {
|
|
|
11681
11936
|
const { cfg } = ctx;
|
|
11682
11937
|
const bundled = cfg.selection.kind === "selected" && bundleApplies(cfg.competition);
|
|
11683
11938
|
const pool = vibePool(localDate((ctx.now ?? /* @__PURE__ */ new Date()).toISOString(), cfg.tz), bundled);
|
|
11684
|
-
const
|
|
11939
|
+
const line = pool[Math.floor(Math.random() * pool.length)];
|
|
11685
11940
|
let liveSeg;
|
|
11686
11941
|
try {
|
|
11687
11942
|
if (cfg.selection.kind !== "selected") throw new Error("no competition");
|
|
@@ -11695,12 +11950,12 @@ function cmdVibe(ctx) {
|
|
|
11695
11950
|
} catch {
|
|
11696
11951
|
}
|
|
11697
11952
|
if (cfg.json) {
|
|
11698
|
-
emitJson({ vibe:
|
|
11953
|
+
emitJson({ vibe: line, tag: "#VibingLaVidaLoca", ...liveSeg ? { live: liveSeg } : {} });
|
|
11699
11954
|
return;
|
|
11700
11955
|
}
|
|
11701
11956
|
const c = painterFor(cfg);
|
|
11702
11957
|
out();
|
|
11703
|
-
out(" \u26BD " + (liveSeg ? `${liveSeg} \u2014 ` : "") + c.bold(
|
|
11958
|
+
out(" \u26BD " + (liveSeg ? `${liveSeg} \u2014 ` : "") + c.bold(line ?? ""));
|
|
11704
11959
|
out(" " + c.cyan("#VibingLaVidaLoca"));
|
|
11705
11960
|
out();
|
|
11706
11961
|
}
|
|
@@ -11714,7 +11969,7 @@ function handlePipeError(stream) {
|
|
|
11714
11969
|
}
|
|
11715
11970
|
handlePipeError(process.stdout);
|
|
11716
11971
|
handlePipeError(process.stderr);
|
|
11717
|
-
var VERSION = "0.11.
|
|
11972
|
+
var VERSION = "0.11.2";
|
|
11718
11973
|
var DISCLAIMER2 = `Claudinho is an ${FAN_PROJECT.toLowerCase()}. ${DISCLAIMER}`;
|
|
11719
11974
|
function ctxFrom(cmd) {
|
|
11720
11975
|
let root = cmd;
|
|
@@ -11813,7 +12068,10 @@ program.command("prompt").description("print a status line (Claude Code / Cursor
|
|
|
11813
12068
|
const cursor = await readCursorPayloadBounded();
|
|
11814
12069
|
cmdPrompt(ctxFrom(cmd), { cursor });
|
|
11815
12070
|
});
|
|
11816
|
-
|
|
12071
|
+
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) => {
|
|
12072
|
+
cmdAmbient(ctxFrom(cmd), { columns: opts.columns });
|
|
12073
|
+
});
|
|
12074
|
+
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
12075
|
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
12076
|
try {
|
|
11819
12077
|
cmdInitCursor(opts, ctxFrom(cmd));
|
|
@@ -11828,6 +12086,13 @@ init.command("claude").description("set up claudinho for Claude Code (statusline
|
|
|
11828
12086
|
fail(e);
|
|
11829
12087
|
}
|
|
11830
12088
|
});
|
|
12089
|
+
init.command("plugin").description("remove the statusline and hook that init claude wrote, for the Claude Code plugin").action((_opts, cmd) => {
|
|
12090
|
+
try {
|
|
12091
|
+
cmdInitPlugin(ctxFrom(cmd));
|
|
12092
|
+
} catch (e) {
|
|
12093
|
+
fail(e);
|
|
12094
|
+
}
|
|
12095
|
+
});
|
|
11831
12096
|
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
12097
|
try {
|
|
11833
12098
|
cmdInitStatusline(opts, ctxFrom(cmd));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claudinho/cli",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "Live football scores, fixtures and standings in your terminal and your Claude Code and Cursor CLI statusline, for the competition you follow: the World Cup, the Premier League, LALIGA, the Champions League and 12 more. No API key. Independent fan project. Not affiliated with FIFA, any confederation, league or club, or Anthropic.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"tsup": "^8.0.0",
|
|
77
77
|
"typescript": "^5.7.0",
|
|
78
78
|
"vitest": "^5.0.3",
|
|
79
|
-
"@claudinho/core": "0.11.
|
|
79
|
+
"@claudinho/core": "0.11.2"
|
|
80
80
|
},
|
|
81
81
|
"scripts": {
|
|
82
82
|
"build": "tsup",
|