@claudinho/cli 0.11.0 → 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.
Files changed (3) hide show
  1. package/README.md +22 -2
  2. package/dist/index.js +338 -72
  3. package/package.json +4 -3
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 11 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 fifteen 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 and the hook 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`, 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. |
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 = /* @__PURE__ */ new Date()) {
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 = /* @__PURE__ */ new Date()) {
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
  }
@@ -4091,6 +4091,7 @@ var SUPPORTED = Object.freeze(
4091
4091
  { slug: "ita.1", alias: "serie-a", name: "Serie A", teams: "club", kind: "league", seasonSlug: "italian-serie-a", standings: "league", bracket: "not-offered-yet", markets: "not-offered-yet", cadenceYears: 1 },
4092
4092
  { slug: "ger.1", alias: "bundesliga", name: "Bundesliga", teams: "club", kind: "league", seasonSlug: "german-bundesliga", standings: "league", bracket: "not-offered-yet", markets: "not-offered-yet", cadenceYears: 1 },
4093
4093
  { slug: "mex.1", alias: "liga-mx", name: "Liga MX", teams: "club", kind: "league", standings: "league", bracket: "not-offered-yet", markets: "not-offered-yet", cadenceYears: 1 },
4094
+ { slug: "uru.1", alias: "liga-auf", name: "Liga AUF Uruguaya", teams: "club", kind: "league", standings: "league", bracket: "not-offered-yet", markets: "not-offered-yet", cadenceYears: 1 },
4094
4095
  { slug: "uefa.champions", alias: "champions-league", name: "Champions League", teams: "club", kind: "cup", standings: "league", bracket: "not-offered-yet", markets: "not-offered-yet", cadenceYears: 1 },
4095
4096
  { slug: "conmebol.libertadores", alias: "libertadores", name: "Libertadores", teams: "club", kind: "cup", standings: "groups", bracket: "not-offered-yet", markets: "not-offered-yet", cadenceYears: 1 },
4096
4097
  { slug: "concacaf.champions", alias: "concacaf-champions-cup", name: "Concacaf Champions Cup", teams: "club", kind: "cup", standings: "none", bracket: "not-offered-yet", markets: "not-offered-yet", cadenceYears: 1 },
@@ -4744,7 +4745,7 @@ function fetchMeta(result) {
4744
4745
  }
4745
4746
  var ESPN_SOCCER = "https://site.api.espn.com/apis/site/v2/sports/soccer";
4746
4747
  var DEFAULT_COMPETITION = BUNDLED_SLUG;
4747
- var USER_AGENT = `claudinho/${"0.11.0"} (+https://github.com/arturogarrido/claudinho)`;
4748
+ var USER_AGENT = `claudinho/${"0.11.2"} (+https://github.com/arturogarrido/claudinho)`;
4748
4749
  var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
4749
4750
  var DEFAULT_COOLDOWN_MS = 5 * 6e4;
4750
4751
  var MAX_COOLDOWN_MS = 15 * 6e4;
@@ -6236,6 +6237,9 @@ function mergeLive(base, live) {
6236
6237
  function clockOf(adapter) {
6237
6238
  return new Date(adapter.now?.() ?? Date.now());
6238
6239
  }
6240
+ function requestClock(adapter, given) {
6241
+ return given ?? clockOf(adapter);
6242
+ }
6239
6243
  function providerDayOf(adapter, instant) {
6240
6244
  if (Number.isNaN(instant.getTime())) return "";
6241
6245
  return adapter.bucketDay?.(instant) || instant.toISOString().slice(0, 10);
@@ -6410,7 +6414,7 @@ async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Dat
6410
6414
  });
6411
6415
  let refreshRead = {};
6412
6416
  if (candidate) {
6413
- const { partial, served: _served, ...r } = await getMatchById(adapter, candidate.id);
6417
+ const { partial, served: _served, ...r } = await getMatchById(adapter, candidate.id, now);
6414
6418
  refreshRead = partial ? { partial } : {};
6415
6419
  const m = r.degraded ? candidate : r.match ?? candidate;
6416
6420
  if (!isFinished(m.status)) return { ...r, match: m, ...bothReads(windowRead, refreshRead) };
@@ -8729,6 +8733,14 @@ function makeT(lang) {
8729
8733
  };
8730
8734
  }
8731
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
+
8732
8744
  // src/tagline.ts
8733
8745
  var NAMED = ["the World Cup", "the Premier League", "LALIGA", "the Champions League"];
8734
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.`;
@@ -8968,7 +8980,9 @@ function readState(source, competition) {
8968
8980
  const bytes = readSmallFile(cachePath(source, competition), MAX_STATE_BYTES);
8969
8981
  if (!bytes) return void 0;
8970
8982
  const parsed = JSON.parse(bytes.toString("utf8"));
8971
- return isCacheState(parsed) ? parsed : void 0;
8983
+ if (!isCacheState(parsed)) return void 0;
8984
+ if (parsed.liveComplete !== void 0 && typeof parsed.liveComplete !== "boolean") delete parsed.liveComplete;
8985
+ return parsed;
8972
8986
  } catch {
8973
8987
  return void 0;
8974
8988
  }
@@ -9447,6 +9461,7 @@ function applyDiscovery(prev, answer, now) {
9447
9461
  // src/statusline.ts
9448
9462
  var DISPLAY_STALE_MS = 5 * 6e4;
9449
9463
  var TOURNAMENT_COMPLETE_LINE = "\u26BD World Cup 2026 is complete \xB7 claudinho follow --list";
9464
+ var NOTHING_KNOWN_LINE = "\u26BD \u2014";
9450
9465
  var FIRST_RUN_LINE = "\u26BD claudinho follow";
9451
9466
  function pickAmbientMatch(matches, pick2) {
9452
9467
  if (!pick2) return [...matches];
@@ -9576,7 +9591,11 @@ function liveMatchesFromCache(state, nowMs, kind) {
9576
9591
  var DEFAULT_MAX_SEGMENTS = 8;
9577
9592
  var MAX_LINE_COLUMNS = 200;
9578
9593
  function renderPrompt(state, opts = {}) {
9579
- return truncateVisible(renderPromptLine(state, opts), MAX_LINE_COLUMNS);
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 };
9580
9599
  }
9581
9600
  function renderPromptLine(state, opts = {}) {
9582
9601
  const now = opts.now ?? /* @__PURE__ */ new Date();
@@ -9588,52 +9607,77 @@ function renderPromptLine(state, opts = {}) {
9588
9607
  const kind = opts.teamKind ?? defaultTeamKind(defaultCompetition);
9589
9608
  const liveList = liveMatchesFromCache(state, nowMs, kind);
9590
9609
  const live = liveList.items;
9591
- const cachedFixtureList = sealFixtures(defaultCompetition ? state?.fixtures : state?.schedule?.fixtures, kind);
9592
- const cachedFixtures = defaultCompetition ? [...cachedFixtureList.items] : cachedFixtureList.items.filter((m) => hasLiveWindow(m.status));
9593
- const bundle = defaultCompetition ? allFixtures() : [];
9594
- const schedule = cachedFixtures.length ? mergeLive(bundle, cachedFixtures) : defaultCompetition ? void 0 : [];
9610
+ const { cachedFixtures, schedule } = countdownSchedule(state, defaultCompetition, kind);
9611
+ const width = lineColumns(opts);
9595
9612
  if (live.length > 0) {
9596
9613
  const ordered = pickAmbientMatch(live, pick2);
9597
9614
  const picked = ordered[0] !== void 0 && isPicked(ordered[0], pick2);
9598
9615
  const max = picked ? 1 : opts.max && opts.max > 0 ? Math.min(opts.max, DEFAULT_MAX_SEGMENTS) : DEFAULT_MAX_SEGMENTS;
9599
9616
  const shown = ordered.slice(0, max);
9600
9617
  const overflow = live.length - shown.length;
9601
- const marker = !liveList.complete ? " +more" : overflow > 0 ? ` +${overflow}` : "";
9602
- const body = "\u26BD " + shown.map((m) => matchSegment(m, compact, flags)).join(" \xB7 ");
9603
- return truncateVisible(body, MAX_LINE_COLUMNS - displayWidth(marker)) + marker;
9618
+ return fitLiveLine(shown.map((m) => matchSegment(m, compact, flags)), overflow, liveList.complete, width);
9604
9619
  }
9605
- const cacheFresh = !!state && state.degraded !== true && ageMs(state, nowMs) < DISPLAY_STALE_MS;
9606
- if ((!cacheFresh || !liveList.complete) && !defaultCompetition) {
9607
- if (scheduleGateOpen(scheduleView(state?.schedule, nowMs), nowMs, { probe: false })) {
9608
- const win = pickAmbientMatch(
9609
- cachedFixtures.filter((m) => {
9610
- const k = Date.parse(m.kickoff);
9611
- return k <= nowMs && nowMs < k + LIVE_WINDOW_MS;
9612
- }).sort(byKickoff),
9613
- pick2
9614
- );
9615
- const first = win[0];
9616
- const matchup = first && isResolvedFixture(first) ? `${teamTok(first.home, flags)} vs ${teamTok(first.away, flags)} ` : "";
9617
- const more = win.length - 1;
9618
- return `\u26BD ${matchup}live \xB7 syncing\u2026` + (more > 0 ? ` +${more}` : "");
9619
- }
9620
- } else if (!cacheFresh || !liveList.complete) {
9621
- const win = pickAmbientMatch(fixturesInLiveWindow(nowMs, schedule), pick2);
9620
+ const win = syncingWindow(state, nowMs, liveWhole(liveList.complete, state), defaultCompetition, cachedFixtures, schedule, pick2);
9621
+ if (win) {
9622
9622
  const first = win[0];
9623
- if (first) {
9624
- const more = win.length - 1;
9625
- const matchup = isResolvedFixture(first) ? `${teamTok(first.home, flags)} vs ${teamTok(first.away, flags)} ` : "";
9626
- return `\u26BD ${matchup}live \xB7 syncing\u2026` + (more > 0 ? ` +${more}` : "");
9627
- }
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);
9628
9626
  }
9629
- const next = pickAmbientMatch(upcomingResolved(nowMs, schedule), pick2)[0];
9627
+ const next = countdownFixture(nowMs, schedule, pick2);
9630
9628
  if (next) {
9631
9629
  return `${teamTok(next.home, flags)} vs ${teamTok(next.away, flags)} in ${countdown(next.kickoff, now)}`;
9632
9630
  }
9633
9631
  if (defaultCompetition && isTournamentWindowOver(nowMs, schedule)) {
9634
9632
  return TOURNAMENT_COMPLETE_LINE;
9635
9633
  }
9636
- return "\u26BD \u2014";
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];
9637
9681
  }
9638
9682
 
9639
9683
  // src/hook.ts
@@ -9651,7 +9695,8 @@ function rosterPinned(t2, pin) {
9651
9695
  const { team } = lookupTeam(t2.code);
9652
9696
  return team ? { ...t2, name: team.name, flag: team.flag } : t2;
9653
9697
  }
9654
- function line(m, flags, pin) {
9698
+ function hookLine(m, opts) {
9699
+ const { flags, pin } = opts;
9655
9700
  const minute = m.status === "HT" ? "half-time" : m.minute ? `${m.minute}'` : "live";
9656
9701
  const h = rosterPinned(m.home, pin);
9657
9702
  const a = rosterPinned(m.away, pin);
@@ -9669,13 +9714,51 @@ function renderHook(state, opts = {}) {
9669
9714
  const live = pickAmbientMatch(liveList.items, opts.pick);
9670
9715
  const shown = live.slice(0, MAX_HOOK_MATCHES);
9671
9716
  const overflow = live.length - shown.length;
9672
- const lines = shown.map((mm) => line(mm, flags, pin)).join("\n");
9717
+ const lines = shown.map((mm) => hookLine(mm, { flags, pin })).join("\n");
9673
9718
  const more = !liveList.complete ? "\n(more live matches may not be shown)" : overflow > 0 ? `
9674
9719
  (+${overflow} more not shown)` : "";
9675
9720
  return boundContext(`[Claudinho \u2014 live football scores right now]
9676
9721
  ${lines}`, more);
9677
9722
  }
9678
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
+
9679
9762
  // src/refresh.ts
9680
9763
  import { spawn } from "child_process";
9681
9764
  var MIN_REFRESH_MS = 12e3;
@@ -9720,6 +9803,8 @@ async function runRefresh(opts) {
9720
9803
  {
9721
9804
  updatedAt: now.toISOString(),
9722
9805
  live: [],
9806
+ liveComplete: false,
9807
+ // nothing was read
9723
9808
  degraded: true,
9724
9809
  // no live provider served this scope — never claim otherwise
9725
9810
  source,
@@ -9753,6 +9838,8 @@ async function runRefresh(opts) {
9753
9838
  {
9754
9839
  updatedAt: now.toISOString(),
9755
9840
  live: [],
9841
+ liveComplete: false,
9842
+ // nothing was read
9756
9843
  degraded: until !== void 0,
9757
9844
  source,
9758
9845
  competition,
@@ -9795,6 +9882,7 @@ async function runRefresh(opts) {
9795
9882
  const { needLive, needFixtures } = plan(base);
9796
9883
  if (!needLive && !needFixtures) return;
9797
9884
  let live = base?.live ?? [];
9885
+ let liveComplete = base ? base.liveComplete : false;
9798
9886
  let degraded = base?.degraded ?? false;
9799
9887
  let updatedAt = base?.updatedAt ?? now.toISOString();
9800
9888
  let fixtures = base?.fixtures;
@@ -9807,11 +9895,13 @@ async function runRefresh(opts) {
9807
9895
  const adapter = liveAdapter(source, competition, clock);
9808
9896
  if (needLive) {
9809
9897
  try {
9810
- const r = await getLiveMatches(adapter, now);
9898
+ const r = await getLiveRead(adapter, now);
9811
9899
  live = r.matches;
9900
+ liveComplete = r.complete === true;
9812
9901
  degraded = r.degraded;
9813
9902
  if (!r.degraded) season = r.season;
9814
9903
  } catch {
9904
+ liveComplete = false;
9815
9905
  degraded = true;
9816
9906
  }
9817
9907
  updatedAt = now.toISOString();
@@ -9846,6 +9936,7 @@ async function runRefresh(opts) {
9846
9936
  {
9847
9937
  updatedAt,
9848
9938
  live,
9939
+ ...liveComplete !== void 0 ? { liveComplete } : {},
9849
9940
  degraded,
9850
9941
  source,
9851
9942
  competition,
@@ -9934,6 +10025,7 @@ async function refreshOffBundle(c) {
9934
10025
  const clock = () => nowMs + (Date.now() - realStart);
9935
10026
  const adapter = liveAdapter(source, competition, clock);
9936
10027
  let live = base?.live ?? [];
10028
+ let liveComplete = base ? base.liveComplete : false;
9937
10029
  let degraded = base?.degraded ?? false;
9938
10030
  let updatedAt = base?.updatedAt ?? NEVER;
9939
10031
  let season = sealSeason(base?.season);
@@ -9950,6 +10042,7 @@ async function refreshOffBundle(c) {
9950
10042
  const snapshot = (backoffUntil2) => ({
9951
10043
  updatedAt,
9952
10044
  live,
10045
+ ...liveComplete !== void 0 ? { liveComplete } : {},
9953
10046
  degraded,
9954
10047
  source,
9955
10048
  competition,
@@ -10005,11 +10098,13 @@ async function refreshOffBundle(c) {
10005
10098
  try {
10006
10099
  const r = await getLiveRead(adapter, now);
10007
10100
  live = r.matches;
10101
+ liveComplete = r.complete === true;
10008
10102
  degraded = r.degraded;
10009
10103
  if (!r.degraded) season = r.season;
10010
10104
  if (r.matches.length > 0) inPlayUntil = raiseInPlay(inPlayUntil, r.matches, nowMs);
10011
10105
  else if (!r.degraded && r.complete) inPlayUntil = void 0;
10012
10106
  } catch {
10107
+ liveComplete = false;
10013
10108
  degraded = true;
10014
10109
  }
10015
10110
  updatedAt = new Date(liveAt).toISOString();
@@ -10126,6 +10221,9 @@ function manual(path, snippet, why) {
10126
10221
  ${snippet}` };
10127
10222
  }
10128
10223
  function readSettings(path, snippet) {
10224
+ return readSettingsOr(path, (why) => manual(path, snippet, why));
10225
+ }
10226
+ function readSettingsOr(path, refuse) {
10129
10227
  let entry;
10130
10228
  try {
10131
10229
  entry = lstatSync3(path);
@@ -10133,15 +10231,15 @@ function readSettings(path, snippet) {
10133
10231
  return {};
10134
10232
  }
10135
10233
  if (entry.isSymbolicLink() && !existsSync(path)) {
10136
- return manual(path, snippet, "Settings path is a symlink to a missing file:");
10234
+ return refuse("Settings path is a symlink to a missing file:");
10137
10235
  }
10138
10236
  let parsed;
10139
10237
  try {
10140
10238
  parsed = JSON.parse(readFileSync2(path, "utf8"));
10141
10239
  } catch {
10142
- return manual(path, snippet, "Could not parse");
10240
+ return refuse("Could not parse");
10143
10241
  }
10144
- if (!isSettingsObject(parsed)) return manual(path, snippet, "Not a JSON settings object:");
10242
+ if (!isSettingsObject(parsed)) return refuse("Not a JSON settings object:");
10145
10243
  return parsed;
10146
10244
  }
10147
10245
  function isInitResult(v) {
@@ -10232,6 +10330,86 @@ function initHook(opts = {}) {
10232
10330
  message: `Live-score hook configured in ${path}. Restart Claude Code; during matches, the score is injected into context on each prompt.`
10233
10331
  };
10234
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
+ }
10235
10413
 
10236
10414
  // src/providerBackoff.ts
10237
10415
  function persistBackoff(source, competition, until, nowMs) {
@@ -10356,8 +10534,8 @@ async function reliableMarketSignalFor(ctx, match) {
10356
10534
  const result = await reliableMarketSignals(ctx, [match]);
10357
10535
  return { signal: result.signals.get(match.id), complete: result.complete };
10358
10536
  }
10359
- function out(line2 = "") {
10360
- process.stdout.write(line2 + "\n");
10537
+ function out(line = "") {
10538
+ process.stdout.write(line + "\n");
10361
10539
  }
10362
10540
  function emitJson(data) {
10363
10541
  out(JSON.stringify(data, null, 2));
@@ -10442,13 +10620,16 @@ async function nextAsked(ctx, team, usage) {
10442
10620
  const { cfg, t: t2 } = ctx;
10443
10621
  const asked = teamAsked(team, cfg);
10444
10622
  if (!asked) throw new InputError(usage);
10445
- const now = ctx.now ?? /* @__PURE__ */ new Date();
10446
10623
  if ("pin" in asked) {
10447
10624
  const code2 = bundleApplies(cfg.competition) ? asked.pin.code : asked.pin.name;
10448
- return { code: code2, next: await nextFixtureForPin(adapterFor(ctx), asked.pin, now) };
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 };
10449
10628
  }
10450
10629
  const code = teamQuery(asked.query, usage, t2, cfg.competition);
10451
- return { code, next: await getNextFixtureForTeam(adapterFor(ctx), code, now) };
10630
+ const adapter = adapterFor(ctx);
10631
+ const now = requestClock(adapter, ctx.now);
10632
+ return { code, next: await getNextFixtureForTeam(adapter, code, now), now };
10452
10633
  }
10453
10634
  function nextEmptySentence(next, code, label2, cfg, t2) {
10454
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 }));
@@ -10462,12 +10643,23 @@ function resolveEnvTeam(raw, competition) {
10462
10643
  return /^[A-Za-z]{3}$/.test(raw) ? raw.toUpperCase() : void 0;
10463
10644
  }
10464
10645
  function ambientPick(cfg) {
10646
+ return ambientPreference(cfg).pick;
10647
+ }
10648
+ function ambientPreference(cfg) {
10465
10649
  const raw = process.env.CLAUDINHO_TEAM;
10466
10650
  if (raw) {
10467
10651
  const code = resolveEnvTeam(raw, cfg.competition);
10468
- return code ? { code } : void 0;
10652
+ return code ? { pick: { code }, unreadable: false } : { pick: void 0, unreadable: true };
10469
10653
  }
10470
- 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 };
10471
10663
  }
10472
10664
  async function cmdToday(date, ctx) {
10473
10665
  const { cfg, t: t2 } = ctx;
@@ -10568,7 +10760,7 @@ async function cmdLive(ctx) {
10568
10760
  async function cmdNext(team, ctx) {
10569
10761
  const { cfg, t: t2 } = ctx;
10570
10762
  precheck(cfg, t2);
10571
- const { code, next } = await nextAsked(ctx, team, teamUsage("next", cfg));
10763
+ const { code, next, now } = await nextAsked(ctx, team, teamUsage("next", cfg));
10572
10764
  const { fixture, degraded, source } = next;
10573
10765
  const label2 = next.team?.name ?? next.query ?? code;
10574
10766
  if (cfg.json) {
@@ -10614,7 +10806,7 @@ async function cmdNext(team, ctx) {
10614
10806
  out(
10615
10807
  " " + c.dim(
10616
10808
  joinSegments(
10617
- 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)]
10618
10810
  )
10619
10811
  )
10620
10812
  );
@@ -10821,11 +11013,7 @@ function cmdPrompt({ cfg, now }, io = {}) {
10821
11013
  out(renderPromptOutput("\u26BD \u2014", payload));
10822
11014
  return;
10823
11015
  }
10824
- const compact = !["0", "false", "no"].includes(
10825
- (process.env.CLAUDINHO_COMPACT ?? "").toLowerCase()
10826
- );
10827
- const maxRaw = Number.parseInt(process.env.CLAUDINHO_MAX ?? "", 10);
10828
- const max = Number.isFinite(maxRaw) && maxRaw > 0 ? maxRaw : void 0;
11016
+ const { compact, max } = promptLineEnv();
10829
11017
  const state = readCurrentState(cfg.source, cfg.competition);
10830
11018
  const scoreLine = renderPrompt(state, {
10831
11019
  // CLAUDINHO_TEAM by code (offline lookup — hot-path safe), else the saved pin.
@@ -10872,6 +11060,67 @@ function cmdHook({ cfg, now }) {
10872
11060
  } catch {
10873
11061
  }
10874
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
+ }
10875
11124
  async function cmdRefresh({ cfg }) {
10876
11125
  if (cfg.selection.kind !== "selected") return;
10877
11126
  await runRefresh({ source: cfg.source, competition: cfg.competition });
@@ -10910,7 +11159,6 @@ var CURSOR_MCP_SNIPPET = `{
10910
11159
  "claudinho": { "command": "npx", "args": ["-y", "@claudinho/mcp"] }
10911
11160
  }
10912
11161
  }`;
10913
- var CLAUDE_MCP_ONELINER = "claude mcp add claudinho -- npx -y @claudinho/mcp";
10914
11162
  function cmdInitCursor(opts, { cfg }) {
10915
11163
  if (opts.print) {
10916
11164
  out("# 1) Cursor CLI statusline \u2192 ~/.cursor/cli-config.json");
@@ -10956,12 +11204,17 @@ function cmdInitClaude(opts, { cfg }) {
10956
11204
  printInitChoose(cfg);
10957
11205
  if (statusRes.action === "written" || hookRes.action === "written") printInitStarCta(cfg);
10958
11206
  }
11207
+ function cmdInitPlugin({ cfg }) {
11208
+ printInitResult(initPlugin(), cfg);
11209
+ }
10959
11210
  async function cmdMatch(id, ctx) {
10960
11211
  const { cfg, t: t2 } = ctx;
10961
11212
  precheck(cfg, t2);
10962
- const found = await getMatchById(adapterFor(ctx), id, ctx.now);
11213
+ const adapter = adapterFor(ctx);
11214
+ const now = requestClock(adapter, ctx.now);
11215
+ const found = await getMatchById(adapter, id, now);
10963
11216
  const { match, degraded, source: liveSource } = found;
10964
- 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 };
10965
11218
  if (cfg.json) {
10966
11219
  emitJson({
10967
11220
  degraded,
@@ -11039,7 +11292,7 @@ function noSignalLine(m, now, competition) {
11039
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.";
11040
11293
  }
11041
11294
  function printMarketBlock(m, sig, c) {
11042
- for (const line2 of marketBlock(sig, m)) out(" " + c.dim(line2));
11295
+ for (const line of marketBlock(sig, m)) out(" " + c.dim(line));
11043
11296
  }
11044
11297
  async function cmdMarkets(target, team, ctx) {
11045
11298
  const { cfg, t: t2 } = ctx;
@@ -11096,7 +11349,6 @@ async function cmdMarkets(target, team, ctx) {
11096
11349
  }
11097
11350
  if (target && target !== "today" && !isValidDate(target)) {
11098
11351
  precheck(cfg, t2);
11099
- const now2 = ctx.now ?? /* @__PURE__ */ new Date();
11100
11352
  const scope = marketScopeVerdict(cfg.competition, 0);
11101
11353
  const outOfScope = verdictNotice(scope, cfg.lang);
11102
11354
  if (outOfScope !== void 0) {
@@ -11120,7 +11372,9 @@ async function cmdMarkets(target, team, ctx) {
11120
11372
  out(c3.dim(MARKET_INFO));
11121
11373
  return;
11122
11374
  }
11123
- const found = await getMatchById(adapterFor(ctx), target);
11375
+ const adapter = adapterFor(ctx);
11376
+ const now2 = requestClock(adapter, ctx.now);
11377
+ const found = await getMatchById(adapter, target, now2);
11124
11378
  const { match } = found;
11125
11379
  const market = match && marketRelevant(match, now2) ? await marketSignalsFor(ctx, [match], MARKETS_CMD_OPTS) : { signals: /* @__PURE__ */ new Map(), complete: true };
11126
11380
  const sig = match ? market.signals.get(match.id) : void 0;
@@ -11355,15 +11609,17 @@ async function cmdShare(target, team, opts, ctx) {
11355
11609
  }
11356
11610
  if (target === "next") {
11357
11611
  precheck(cfg, t2);
11358
- const { code, next } = await nextAsked(ctx, team, teamUsage("share next", cfg));
11359
- 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] : []);
11360
11614
  emitMatchCard(ctx, nextShareCard(next, code, market2, where), baseOptions, copy);
11361
11615
  return;
11362
11616
  }
11363
11617
  if (target && target !== "today" && !isValidDate(target)) {
11364
11618
  precheck(cfg, t2);
11365
- const found = await getMatchById(adapterFor(ctx), target, ctx.now);
11366
- const market2 = await reliableShareSignals(ctx, found.match ? [found.match] : []);
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] : []);
11367
11623
  emitMatchCard(ctx, matchShareCard(found, target, market2, where), baseOptions, copy);
11368
11624
  return;
11369
11625
  }
@@ -11680,7 +11936,7 @@ function cmdVibe(ctx) {
11680
11936
  const { cfg } = ctx;
11681
11937
  const bundled = cfg.selection.kind === "selected" && bundleApplies(cfg.competition);
11682
11938
  const pool = vibePool(localDate((ctx.now ?? /* @__PURE__ */ new Date()).toISOString(), cfg.tz), bundled);
11683
- const line2 = pool[Math.floor(Math.random() * pool.length)];
11939
+ const line = pool[Math.floor(Math.random() * pool.length)];
11684
11940
  let liveSeg;
11685
11941
  try {
11686
11942
  if (cfg.selection.kind !== "selected") throw new Error("no competition");
@@ -11694,12 +11950,12 @@ function cmdVibe(ctx) {
11694
11950
  } catch {
11695
11951
  }
11696
11952
  if (cfg.json) {
11697
- emitJson({ vibe: line2, tag: "#VibingLaVidaLoca", ...liveSeg ? { live: liveSeg } : {} });
11953
+ emitJson({ vibe: line, tag: "#VibingLaVidaLoca", ...liveSeg ? { live: liveSeg } : {} });
11698
11954
  return;
11699
11955
  }
11700
11956
  const c = painterFor(cfg);
11701
11957
  out();
11702
- out(" \u26BD " + (liveSeg ? `${liveSeg} \u2014 ` : "") + c.bold(line2 ?? ""));
11958
+ out(" \u26BD " + (liveSeg ? `${liveSeg} \u2014 ` : "") + c.bold(line ?? ""));
11703
11959
  out(" " + c.cyan("#VibingLaVidaLoca"));
11704
11960
  out();
11705
11961
  }
@@ -11713,7 +11969,7 @@ function handlePipeError(stream) {
11713
11969
  }
11714
11970
  handlePipeError(process.stdout);
11715
11971
  handlePipeError(process.stderr);
11716
- var VERSION = "0.11.0";
11972
+ var VERSION = "0.11.2";
11717
11973
  var DISCLAIMER2 = `Claudinho is an ${FAN_PROJECT.toLowerCase()}. ${DISCLAIMER}`;
11718
11974
  function ctxFrom(cmd) {
11719
11975
  let root = cmd;
@@ -11812,7 +12068,10 @@ program.command("prompt").description("print a status line (Claude Code / Cursor
11812
12068
  const cursor = await readCursorPayloadBounded();
11813
12069
  cmdPrompt(ctxFrom(cmd), { cursor });
11814
12070
  });
11815
- var init = program.command("init").description("one-step setup for your agent: `init cursor` or `init claude`");
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");
11816
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) => {
11817
12076
  try {
11818
12077
  cmdInitCursor(opts, ctxFrom(cmd));
@@ -11827,6 +12086,13 @@ init.command("claude").description("set up claudinho for Claude Code (statusline
11827
12086
  fail(e);
11828
12087
  }
11829
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
+ });
11830
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) => {
11831
12097
  try {
11832
12098
  cmdInitStatusline(opts, ctxFrom(cmd));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@claudinho/cli",
3
- "version": "0.11.0",
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 11 more. No API key. Independent fan project. Not affiliated with FIFA, any confederation, league or club, or Anthropic.",
3
+ "version": "0.11.2",
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",
7
7
  "author": "Arturo Garrido",
@@ -59,6 +59,7 @@
59
59
  "serie-a",
60
60
  "bundesliga",
61
61
  "liga-mx",
62
+ "liga-auf",
62
63
  "champions-league",
63
64
  "libertadores",
64
65
  "concacaf-champions-cup",
@@ -75,7 +76,7 @@
75
76
  "tsup": "^8.0.0",
76
77
  "typescript": "^5.7.0",
77
78
  "vitest": "^5.0.3",
78
- "@claudinho/core": "0.11.0"
79
+ "@claudinho/core": "0.11.2"
79
80
  },
80
81
  "scripts": {
81
82
  "build": "tsup",