@claudinho/cli 0.11.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +326 -249
  2. package/dist/index.js +1085 -200
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -1,333 +1,410 @@
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.
4
-
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.)
3
+ **Live football scores, fixtures and standings in your terminal, for the competition you follow: the World Cup, the Premier League, LALIGA, the Champions League and 12 more.**
4
+ The `claudinho` command also puts the live score in your Claude Code or Cursor CLI statusline. It
5
+ speaks English, Spanish, Portuguese and French, follows your timezone, and prints JSON for scripts.
6
+ No API key, no signup, no account.
6
7
 
7
8
  ## Install
8
9
 
10
+ It needs Node.js 20 or later.
11
+
9
12
  ```bash
10
- npm i -g @claudinho/cli # installs the `claudinho` binary
11
- claudinho follow world-cup # choose a competition once (claudinho follow --list shows them all)
12
- # or run without installing, choosing per command (or once: npx @claudinho/cli follow world-cup):
13
- npx @claudinho/cli --competition world-cup today
13
+ npm i -g @claudinho/cli
14
+ claudinho follow champions-league # choose your competition once (every alias is in the table below)
15
+ claudinho today
14
16
  ```
15
17
 
16
- `claudinho today` on a World Cup knockout night, penalty shootouts and all:
18
+ Or run it without installing:
17
19
 
18
- <!-- DEMO: verbatim `claudinho --tz America/Los_Angeles today 2026-06-29` with the World Cup
19
- followed (`claudinho follow world-cup`), from a knockout matchday. Shootouts render as 1(3)–1(4). REGENERATE per matchday
20
- (capture after the day's games finish, so the scores are live and current). Never
21
- hand-edit. Re-rendered on 2026-10-05 (0.11) through the CLI's own `today` command,
22
- offline: a provider double served the capture's three results on the bundled
23
- fixtures' ids, at a frozen clock of 2026-06-30T12:00Z. A US zone, because the capture's
24
- was west of UTC: in UTC the third tie (01:00Z on Jun 30) leaves this date. -->
25
- ```text
26
- Matches · 2026-06-29
27
- World Cup
20
+ ```bash
21
+ npx @claudinho/cli follow champions-league
22
+ npx @claudinho/cli today --lang es --tz America/Mexico_City
23
+ ```
28
24
 
29
- 🇧🇷 Brazil 2–1 Japan 🇯🇵 FT into the history books!
30
- 🇩🇪 Germany 1(3)–1(4) Paraguay 🇵🇾 FT it's all over!
31
- 🇳🇱 Netherlands 1(2)–1(3) Morocco 🇲🇦 FT the final whistle blows!
25
+ <!-- DEMO: verbatim `claudinho --tz UTC today` with the Champions League followed, on Oct 14, 2026
26
+ after the day's games, colour off. Never hand-edit. -->
27
+ ```text
28
+ Today's matches · 2026-10-14
29
+ Champions League
30
+
31
+ Feyenoord Rotterdam 3–2 Como FT the scoreboard has spoken!
32
+ LASK Linz 3–6 Liverpool FT You'll never walk alone!
33
+ AS Roma 3–4 Real Madrid FT Daje Roma!
34
+ Aston Villa 6–0 Fenerbahce FT UTV!
35
+ Bodo/Glimt 1–0 Borussia Dortmund FT Echte Liebe!
36
+ Manchester City 2–4 Paris Saint-Germain FT Blue Moon!
37
+ Real Betis 3–2 FC Porto FT ¡Viva er Betis manque pierda!
38
+ Shakhtar Donetsk 3–1 AEK Athens FT off to the tunnel!
39
+ Slovan Bratislava 0–3 VfB Stuttgart FT the referee calls time!
32
40
 
33
41
  Live data: ESPN
34
42
  Not affiliated with FIFA, any confederation, league or club, or Anthropic.
35
43
  ```
36
44
 
37
- The World Cup's schedule ships bundled (104 fixtures), so it works offline and only its live scores hit the network; the other competitions' schedules are read from the feed, so `today` and `next` need the network there.
45
+ This is a capture from Oct 14, 2026. `claudinho --tz UTC today 2026-10-14` shows that day's results
46
+ again.
47
+
48
+ ## Competitions
49
+
50
+ The 16 competitions Claudinho follows, by the alias `follow` and `--competition` take:
51
+
52
+ <!-- competitions:start -->
53
+ | Alias | Competition | Teams | Scores | Next | Standings | Bracket | Markets |
54
+ |---|---|---|---|---|---|---|---|
55
+ | `world-cup` | World Cup | nations | yes | yes | yes | yes | yes |
56
+ | `euro` | EURO | nations | yes | yes | yes | not yet | not yet |
57
+ | `copa-america` | Copa América | nations | yes | yes | yes | not yet | not yet |
58
+ | `nations-league` | UEFA Nations League | nations | yes | yes | yes | not yet | not yet |
59
+ | `concacaf-nations-league` | Concacaf Nations League | nations | yes | yes | yes | not yet | not yet |
60
+ | `gold-cup` | Gold Cup | nations | yes | yes | yes | not yet | not yet |
61
+ | `premier-league` | Premier League | clubs | yes | yes | yes | n/a | not yet |
62
+ | `laliga` | LALIGA | clubs | yes | yes | yes | n/a | not yet |
63
+ | `serie-a` | Serie A | clubs | yes | yes | yes | not yet | not yet |
64
+ | `bundesliga` | Bundesliga | clubs | yes | yes | yes | not yet | not yet |
65
+ | `liga-mx` | Liga MX | clubs | yes | yes | yes | not yet | not yet |
66
+ | `liga-auf` | Liga AUF Uruguaya | clubs | yes | yes | yes | not yet | not yet |
67
+ | `champions-league` | Champions League | clubs | yes | yes | yes | not yet | not yet |
68
+ | `libertadores` | Libertadores | clubs | yes | yes | yes | not yet | not yet |
69
+ | `concacaf-champions-cup` | Concacaf Champions Cup | clubs | yes | yes | n/a | not yet | not yet |
70
+ | `club-world-cup` | Club World Cup | clubs | yes | yes | yes | not yet | not yet |
71
+
72
+ `yes` offered · `not yet` not offered yet · `n/a` the competition has no such thing (a league season with no knockout tie has no bracket; a knockout-only cup has no table).
73
+ <!-- competitions:end -->
38
74
 
39
75
  ## Commands
40
76
 
41
77
  ```bash
42
- claudinho follow [ALIAS] # choose the competition every surface follows (saved); --team <name> pins your team; --list; off
78
+ claudinho follow [ALIAS] # choose the competition you follow (saved); --team <name> pins your team; --list; off
43
79
  claudinho today [date] # a day's fixtures in your timezone (default: today), live scores inline
44
80
  claudinho live # matches in play right now
45
- claudinho next [TEAM] # a team's next fixture + countdown — TEAM is a name OR code (Mexico | MEX | "DR Congo", or a club: Arsenal | ARS); default $CLAUDINHO_TEAM, else your pinned team
46
- claudinho table [KEY] # live cumulative standings (default: every table); KEY is a group letter, or A1, A-B, LEAGUE
47
- claudinho bracket [STAGE] # knockout bracket (R32, R16, QF, SF, 3P, F); --tree for ASCII tree
48
- claudinho match <id> # a single match's detail
49
- claudinho team <query> # resolve a World Cup nation's name/code to its FIFA code, flag, and group (e.g. team "DR Congo")
50
- claudinho markets [target] # prediction-market signals: today | <date> | <id> | next <TEAM>
51
- # (next prefers the team's IN-PLAY match while one is live)
52
- claudinho share [target] # copy-pasteable snippet: today | live | <date> | <id> | next <TEAM> | table <KEY> | bracket [STAGE]
53
- claudinho prompt # one compact status line (for statusline/tmux/Starship)
54
- claudinho init cursor # one-step Cursor setup: statusline + MCP paste (--print for snippets)
55
- claudinho init claude # one-step Claude Code setup: statusline + hook + MCP one-liner
56
- claudinho init-statusline # (granular) wire just the Claude Code statusline
57
- claudinho init-cursor-statusline # (granular) wire just the Cursor CLI statusline
58
- claudinho hook # live-score context for a Claude Code hook (silent off-match)
59
- claudinho init-hook # (granular) make Claude itself score-aware (UserPromptSubmit)
60
- claudinho vibe # a matchday-coder one-liner (#VibingLaVidaLoca)
61
- claudinho star # how to support the project (star the repo ⭐)
81
+ claudinho next [TEAM] # a team's next match and a countdown; TEAM is a name or a code (Arsenal, ARS, Mexico, MEX); default: CLAUDINHO_TEAM, else your pin
82
+ claudinho table [KEY] # standings (default: every table); KEY is a group letter, or A1, A-B, LEAGUE
83
+ claudinho match <id> # one match, with its goals and cards
84
+ claudinho bracket [STAGE] # the knockout bracket, World Cup only (R32, R16, QF, SF, 3P, F); --tree for an ASCII tree
85
+ claudinho share [target] # a plain-text card to paste: today | live | <date> | <id> | next [TEAM] | table [KEY] | bracket [STAGE]
86
+ claudinho markets [target] # World Cup prediction-market odds: today | <date> | <id> | next [TEAM]
87
+ claudinho team <query> # a World Cup nation's name or code to its FIFA code, flag and group (team "DR Congo")
88
+ claudinho prompt # one compact status line (for a statusline, tmux or Starship)
89
+ claudinho init claude # Claude Code: statusline + score-aware hook, then the MCP line to run
90
+ claudinho init cursor # Cursor CLI: statusline, then the MCP config to paste (--print for the snippets)
91
+ claudinho init plugin # remove the Claudinho statusline and hook from Claude Code (for the Claude Code plugin)
92
+ claudinho init-statusline # just the Claude Code statusline
93
+ claudinho init-hook # just the Claude Code score-aware hook
94
+ claudinho init-cursor-statusline # just the Cursor CLI statusline
95
+ claudinho hook # the live-score block for a Claude Code hook (silent off-match)
96
+ claudinho ambient --json # the statusline and the hook as one JSON object, for a program
97
+ claudinho vibe # a matchday-coder one-liner
98
+ claudinho star # how to support the project
62
99
  ```
63
100
 
64
- In a club competition, `next` resolves a club's name or code against the
65
- competition's own table and answers its next match within 14 days (several teams
66
- matching a name are listed, never guessed); `match <id>` looks from yesterday to
67
- 14 days ahead and names the days it searched when the match is not there;
68
- `bracket` says when a league season has no bracket; and `today`, `live`, `next`
69
- and `match` say "between editions" once a competition's edition has ended and
70
- the next has not started. A club shows by its name (or its code on the
71
- statusline: `ARS 2–1 CHE 50'`), with nothing where a nation's flag would be, and
72
- `next`/`match` say the stage it is in ("League", "Play-offs", or the provider's
73
- words for a phase Claudinho does not know).
74
-
75
- `claudinho follow <alias>` saves the competition every surface follows;
76
- `--team <name>` pins a team in it, resolved as `next <name>` resolves it. Pin a
77
- team while its competition has fixtures ahead: between editions `next` answers
78
- the verdict before it resolves a club, so there is nothing to pin (a `follow
79
- <alias>` without `--team` still works there). A pin is scoped to its
80
- competition, whatever chose it (`--competition`, `CLAUDINHO_COMPETITION`, or the
81
- saved choice), and never applies to another. `follow --json` says the same facts as
82
- its text: `competition` (`follow` alone: this command's; after a write or `off`: what
83
- the next command follows), `override` (its source when it is not the saved choice),
84
- `sources` (the flag, the environment and `CLAUDINHO_TEAM` this command ran under),
85
- `refused` (each refused value under its source's name, `flag`, `env` or `team`; a
86
- `CLAUDINHO_TEAM` with nothing readable is `"team": ""`, which the team-taking
87
- commands refuse), `saved` (the file as read) and `path`.
88
-
89
101
  ### Examples
90
102
 
91
103
  ```bash
104
+ claudinho follow premier-league --team Arsenal # follow a league and pin a team
105
+ claudinho next # Arsenal's next match, with a countdown
92
106
  claudinho today --tz America/Mexico_City --lang es
93
- claudinho next BRA --tz America/Sao_Paulo --lang pt
94
- claudinho table A
95
- claudinho bracket R32 --tree
107
+ claudinho --competition liga-mx table # another competition, for one command
96
108
  claudinho live --json | jq '.matches[].status'
97
- claudinho today --flavor off # just the facts, no commentary
98
- claudinho share next MEX --copy # a shareable card, copied to your clipboard
99
- claudinho follow premier-league --team Arsenal # follow a competition, pin a team: `claudinho next` answers for it
100
- claudinho --competition laliga today # another competition for one command, by its alias
101
- CLAUDINHO_COMPETITION=laliga claudinho table # the environment works too (the statusline and hook follow it)
109
+ claudinho share next --copy # a card for the group chat, on your clipboard
102
110
  ```
103
111
 
104
- ## Global options
112
+ ### In a club competition
113
+
114
+ `next` finds a club by its name or code among the competition's clubs (its table, and its fixtures
115
+ from yesterday to 14 days ahead) and answers its next match within 14 days. When several clubs match a name it
116
+ lists them and does not guess. `match <id>` looks from yesterday to 14 days ahead. When the match is not there it names the days it
117
+ searched, or, when the provider's answer was not whole, says no match with that id was read; when
118
+ the provider cannot be reached, it says so. Between two seasons, `today`, `live`, `next` and
119
+ `match` say "Between editions". `bracket` says "This competition has no bracket." on the Premier
120
+ League and LALIGA, and "Not available for this competition yet." on every other competition but the
121
+ World Cup. A club shows by its name (by its code
122
+ on the statusline: `ARS 2–1 CHE 50'`), with no flag, and `next` and `match` say the stage:
123
+ "League", "Play-offs", or the provider's own words for a round Claudinho does not know.
124
+
125
+ ### One match
126
+
127
+ `match <id>` lists the goals and cards the provider sent for a match in play or finished, one row
128
+ each: the minute (`90+3'` with added time), the kind (goal, own goal, penalty, yellow or red card),
129
+ the side and the player when named, a marker before the kind (`⚽` for a goal, an own goal or a penalty, `🟨` for a
130
+ yellow card, `🟥` for a red card): `22' ⚽ Goal LIV Ronald Araújo`. An own goal sits under the side it counted for.
131
+ The list is what could be read, not a promise that it holds every goal. The answer opens with the score in bold, your
132
+ pinned team's name in colour, and a status row (`FT · Kickoff Wed 16:45`). With `--json` it adds `lines` (the rows
133
+ the text prints), `rows` (the same rows as styled parts with their roles, for a program that draws them),
134
+ `attribution` and `disclaimer`.
135
+
136
+ ### When the data is not whole
137
+
138
+ When the provider sends a record Claudinho cannot read, `today`, `live`, `next`, `match`, `bracket`,
139
+ `markets` and their share cards say their data may be incomplete. `today` and `live` say it after
140
+ their list; the others, and every share card, before what they show. With
141
+ `--json` they carry `partial` (with `omitted`, the count of records left out, when it is known). An
142
+ empty answer then means nothing was read, not that nothing exists: no next match does not mean a
143
+ team is out.
144
+
145
+ ## Choosing a competition
146
+
147
+ Nothing is followed until you choose. Three sources can choose, and the first one present wins:
148
+
149
+ 1. `--competition <alias>` (or `-c`), for one command;
150
+ 2. `CLAUDINHO_COMPETITION`, while it is set (the statusline, the hook and `ambient --json` follow it too);
151
+ 3. the choice `claudinho follow <alias>` saved.
152
+
153
+ Each takes an alias from the table, or an ESPN slug such as `eng.1`. Any other lower-case dotted
154
+ ESPN slug works too, labelled experimental. An unknown value is refused with the list. With none of
155
+ the three, a command that answers for a competition says so and exits 1 (`--json` prints
156
+ `{ "competition": null, "noCompetition": true }`), the statusline reads `⚽ claudinho follow`, and
157
+ the hook stays silent.
158
+
159
+ `today`, `live`, `next`, `match`, `table`, `bracket` and `markets` name their competition near the
160
+ top (`Premier League · from the command line`), and in `--json` as `competition`. A share card names
161
+ it in its title, and `share --json` carries `competition` too. `team` reads the World Cup's nations
162
+ and carries no `competition`; it is refused when `--competition` or `CLAUDINHO_COMPETITION` chooses
163
+ another competition.
164
+
165
+ **Your team.** `claudinho follow <alias> --team <name>` pins a team in that competition. When you
166
+ name no team and `CLAUDINHO_TEAM` is not set, it is the team `next`, `share next` and `markets next`
167
+ answer for, and its match comes first on the statusline and in the hook. A pin belongs to its
168
+ competition and never applies to another, whatever chose the competition. Between two seasons
169
+ ("between editions") `follow --team` saves nothing: pin your team once the next season has started.
170
+ A club with no match in the next 14 days can still be pinned.
171
+
172
+ **`CLAUDINHO_TEAM`** replaces the pin while it is set. `next`, `share next` and `markets next` take it
173
+ as a name or a code. The statusline and the hook take a code (`ARS`); on the World Cup a nation's
174
+ name works too (`mexico`). Off the World Cup a club's name there puts no team first, and the pin is
175
+ not used either.
176
+
177
+ **`claudinho follow`** shows the choice and where it came from; `claudinho follow off` forgets it
178
+ (it deletes `config.json`). `follow --json` prints the same facts as its text:
179
+
180
+ - `competition`: with `follow` alone, what this command follows (`--competition`, else
181
+ `CLAUDINHO_COMPETITION`, else the saved choice). After a write or `off`, what the next command
182
+ follows: while `CLAUDINHO_COMPETITION` is set, that is the environment's choice, not the one just
183
+ saved. With nothing chosen it is `null`, beside `noCompetition: true`; when the value that decides
184
+ it is refused, it is `null`, beside `refused` (a refused value that does not decide it is only in
185
+ `refused`).
186
+ - `override`: `flag` or `env` when that competition does not come from the saved choice.
187
+ - `sources`: the `--competition`, `CLAUDINHO_COMPETITION` and `CLAUDINHO_TEAM` values this command
188
+ ran under, each competition as its resolved slug (`laliga` is `esp.1`).
189
+ - `refused`: each refused value under `flag`, `env` or `team` (a `CLAUDINHO_TEAM` with nothing
190
+ readable is `"team": ""`).
191
+ - `saved`: the file as read, or as written; `null` when there is none, with `reason` saying why.
192
+ - `path`: the file's path; after `off`, `removed` says whether there was a file.
193
+
194
+ ### Where it is saved
195
+
196
+ Your choice is `config.json` in `$XDG_CONFIG_HOME/claudinho/` when `XDG_CONFIG_HOME` is an absolute
197
+ path, else in `%APPDATA%\claudinho\` on Windows, else in `~/.config/claudinho/`. The cache follows the
198
+ same rule: `$XDG_CACHE_HOME/claudinho/`, else `%LOCALAPPDATA%\claudinho\` on Windows, else
199
+ `~/.cache/claudinho/`. A relative `XDG_*` value is ignored. `claudinho follow off` deletes the
200
+ choice; delete the cache folder yourself after `npm rm -g @claudinho/cli`.
201
+
202
+ ### Other settings
203
+
204
+ `CLAUDINHO_NO_STAR=1` turns off the occasional "star the repo" line under the interactive commands. `CLAUDINHO_DEBUG=1` prints the data provider's failures to stderr.
205
+
206
+ ## Options
105
207
 
106
208
  | Flag | Description |
107
209
  |---|---|
108
- | `--lang <code>` | `en`, `es`, `pt`, `fr` (also via `CLAUDINHO_LANG`; falls back to `$LANG`) |
109
- | `--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
- | `--json` | machine-readable output for scripting |
111
- | `--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 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. |
113
- | `--source <name>` | live data provider (advanced; sensible default) |
114
- | `--flavor <level>` | commentary flair: `off`, `subtle`, `full` (default: `full`; also `CLAUDINHO_FLAVOR`) |
115
- | `--no-markets` | hide prediction-market signals in `today`/`match` (also `CLAUDINHO_MARKETS=off`) |
116
-
117
- Team codes: a nation's is FIFA-style, 3 letters (`MEX`, `BRA`, `USA`, `ENG`); a club's is the provider's abbreviation (`ARS`, `LEE`), and a club is also taken by name (`next Arsenal`).
118
-
119
- ### Commentary flair
120
-
121
- By default Claudinho narrates with a bit of localized football-broadcast energy:
122
- `¡GOOOOL!` on a goal, `¡a cancha llena!` before kickoff, something for the break,
123
- the last ten minutes and a draw. The bank holds a few hundred lines in four
124
- languages (genre exclamations and the catchphrases fans know; no real person is
125
- named or impersonated), localized per `--lang`; a day's list never prints the
126
- same line twice while the bank has another, and none of it reaches `--json`.
127
-
128
- When one side has a rally cry of its own, the cry takes the slot instead, in
129
- every language: Mexico's `¿Y si sí?`, Pumas' `¡Goya!`, Arsenal's `COYG!` (when
130
- both sides have one, the team you pinned with `follow --team`, else the home
131
- side's; `CLAUDINHO_TEAM` picks the team `next` asks about, not the cry). A row
132
- with a cry takes no line from the bank, so the others in the list stay
133
- different; a postponed or cancelled match prints neither.
134
-
135
- - `--flavor full` *(default)*: flair on fixtures, live play, goals, the break, the closing minutes, and full time
136
- - `--flavor subtle`: only goals, full time and draws, and the rally cries
210
+ | `--lang <code>` | `en`, `es`, `pt`, `fr` (also `CLAUDINHO_LANG`; falls back to `$LANG`) |
211
+ | `--tz <zone>` | an IANA timezone, such as `America/Mexico_City` (also `CLAUDINHO_TZ`; default: your system's). Kickoff times **and** the day a match falls on use this zone. |
212
+ | `--json` | machine-readable output for scripts |
213
+ | `--no-color` | no ANSI color (also `NO_COLOR`; off automatically when piped) |
214
+ | `-c, --competition <alias\|slug>` | the competition for this command (see [Choosing a competition](#choosing-a-competition)) |
215
+ | `--flavor <level>` | commentary: `full` (default), `subtle` or `off` (also `CLAUDINHO_FLAVOR`) |
216
+ | `--no-markets` | hide World Cup market odds in `today`, `match` and share cards (also `CLAUDINHO_MARKETS=off`) |
217
+ | `--source <name>` | the live data provider (advanced) |
218
+
219
+ Team codes: a nation's is FIFA's 3 letters (`MEX`, `BRA`, `ENG`); a club's is the provider's
220
+ abbreviation (`ARS`, `LEE`). A club is also found by its name (`next Arsenal`).
221
+
222
+ ### Commentary
223
+
224
+ By default a match line ends with a short line of commentary in your language: `¡GOOOOL!` on a
225
+ goal, `¡a cancha llena!` before kickoff, something for the break and the last ten minutes. The bank
226
+ holds a few hundred lines in four languages, and a day's list does not repeat one while the bank has
227
+ another. When a side has a rally cry of its own, the cry takes the slot (Mexico's `¿Y si sí?`, Pumas'
228
+ `¡Goya!`, Arsenal's `COYG!`; your pinned team's when both sides have one). A postponed or cancelled
229
+ match gets neither. The commentary reaches `--json` only in `match --json`'s `lines` and `rows`,
230
+ which are the text's own rows.
231
+
232
+ - `--flavor full` *(default)*: before kickoff, in play, goals, the break, the closing minutes and full time
233
+ - `--flavor subtle`: goals, full time, draws and the rally cries only
137
234
  - `--flavor off`: just the facts
138
235
 
139
- ## Prediction-market signals
140
-
141
- `claudinho markets` shows **read-only** prediction-market signals ("who's favored" as
142
- market-implied percentages) for a date, a match, or a team's next fixture:
143
-
144
- ```bash
145
- claudinho markets # today's signals
146
- claudinho markets 2026-06-11 # a specific date
147
- claudinho markets 760415 # one match by id
148
- claudinho markets next MEX # a team's current-or-next fixture (in-play preferred)
149
- claudinho markets today --json # structured sidecar output
150
- ```
151
-
152
- A short market line is also added under `claudinho today` and `claudinho match`
153
- when a reliable market is available. It's **informational only, not betting
154
- advice:** market-implied percentages with attribution, no trading, no links. Data
155
- comes from Polymarket public market data and is shown
156
- only when the market maps cleanly to the result and is fresh.
157
- Structured output uses `complete` on `markets` and `marketComplete` on the
158
- default-on `today`/`match` enrichment. `false` means the optional read did not
159
- finish and is paired with an explicit warning, not a confident empty result.
160
-
161
- Opt out with `--no-markets` (per command) or `CLAUDINHO_MARKETS=off` (global). The
162
- statusline and hook **never** show market data: it stays off the hot path.
236
+ ## Shareable cards
163
237
 
164
- > **How matches are matched:** event slugs are derived automatically from each
165
- > fixture (`fifwc-{home}-{away}-{date}`), so real signals appear for any match with a
166
- > live Polymarket market, with no mapping needed (`mapping.2026.json` is for slug
167
- > *overrides* only). Matching fails closed, so an unmatched fixture simply shows
168
- > nothing, and finished matches never show one (market signals are pre-match
169
- > and in-play reads). For an offline preview, set `CLAUDINHO_MARKETS_SOURCE=fake`
170
- > to render clearly-labeled synthetic **"demo data"** signals.
171
-
172
- ## Shareable snippets
173
-
174
- `claudinho share` prints a polished, **copy-pasteable** match card for chats,
175
- social posts, READMEs, and issue comments: your terminal football, ready to post:
238
+ `claudinho share` prints a plain-text card you can paste into a chat, a post or an issue:
176
239
 
177
240
  ```bash
178
241
  claudinho share # today's matches
179
- claudinho share live # matches in play
180
- claudinho share next MEX # a team's next fixture (+ market read, when reliable)
181
- claudinho share table A # a group's standings card (facts only, no market line)
182
- claudinho share bracket # knockout bracket card (facts only, no market line)
183
- claudinho share bracket R16 # one round only
184
- claudinho share 760415 # one match by id
185
- claudinho share next MEX --copy # …and copy it straight to the clipboard
242
+ claudinho share live # the matches in play
243
+ claudinho share next # your team's next match (or next <TEAM>)
244
+ claudinho share table # the standings (or table <KEY> for one table)
245
+ claudinho share 401915407 # one match by id
246
+ claudinho share bracket R16 # the World Cup bracket, or one round (R32, R16, QF, SF, 3P, F)
247
+ claudinho share next --copy # also copy it to the clipboard
186
248
  ```
187
249
 
188
- <!-- DEMO CARD: verbatim output of `claudinho --competition world-cup --tz UTC share next MEX`.
189
- REGENERATE before release: the matchup advances each round and any market block
190
- drifts. Never hand-edit. Re-rendered on 2026-10-05 (0.11) through the CLI's own
191
- `share next MEX` command, offline: a provider double served the Round of 32 tie the
192
- capture showed (Mexico vs Ecuador, on the bundled fixture's id, kickoff and venue),
193
- at a frozen clock of 2026-06-28T12:00Z. -->
250
+ <!-- DEMO CARD: verbatim `claudinho --tz UTC share 401915407` (a Champions League match from Oct 14,
251
+ 2026), with the Champions League followed. Never hand-edit. -->
194
252
  ```text
195
- Next up for Mexico · World Cup
253
+ Match pulse · Champions League
196
254
 
197
- 🇲🇽 Mexico vs Ecuador 🇪🇨
198
- Jul 1 · 01:00 UTC
199
- Estadio Banorte, Mexico City, Mexico
200
- Round of 32
255
+ Manchester City 2–4 Paris Saint-Germain · FT
256
+ Etihad Stadium, Manchester, England
257
+ League phase
201
258
 
202
259
  Live data: ESPN
203
260
  #VibingLaVidaLoca · Independent fan project · Not affiliated with FIFA, any confederation, league or club, or Anthropic.
204
- Try it: npx @claudinho/cli --competition world-cup next MEX
261
+ Try it: npx @claudinho/cli --competition champions-league match 401915407
205
262
  ```
206
263
 
207
- Snippets are **plain text** (no color codes: they paste cleanly everywhere) and
208
- carry the non-affiliation disclaimer on every paste. The market line uses the
209
- same reliable gate as `today`/`match` (**informational only, never betting
210
- advice**) and disappears when no reliable market exists. Per-command options:
264
+ This card is a capture from Oct 14, 2026. Off the World Cup, `share <id>` finds a match from
265
+ yesterday to 14 days ahead, so run it with a current id (`claudinho today --json` lists each
266
+ match's `id`).
267
+
268
+ A card has no color codes, so it pastes cleanly anywhere, and always carries the non-affiliation
269
+ sentence. `--copy` uses `pbcopy` on macOS, `clip` on Windows, and elsewhere the first of `wl-copy`,
270
+ `xclip` and `xsel` that works. When none works, the card is still printed: copy it from the terminal.
211
271
 
212
272
  | Flag | Description |
213
273
  |---|---|
214
- | `--style <social\|compact>` | `social` (default) is the full card; `compact` is one terse line per match |
215
- | `--copy` | also copy the snippet to the clipboard (best-effort: `pbcopy`/`clip`/`wl-copy`/`xclip`/`xsel`) |
216
- | `--no-hashtag` | omit the `#VibingLaVidaLoca` tag |
217
- | `--no-install-line` | omit the `Try it: …` run cue |
218
-
219
- `--json` returns the structured snippet
220
- (`{ kind, snippet, matches, marketSignals, marketComplete, … }`) for scripts and
221
- future reuse. An incomplete optional market read is stated in the snippet and reported
222
- as `marketComplete: false`; it is never presented as a confident empty market result.
223
- When the provider's knockout answer was not whole (a record it sent could not be read,
224
- was a second copy of a fixture, or lay beyond the bound Claudinho reads), `next`,
225
- `bracket` and their share cards say so before what was read, and `--json` carries
226
- `partial` (`{ omitted }`, the count of provider records left out, when known): no fixture
227
- is then not "eliminated". `today`, `live`, `match`, `markets` and their share cards do the
228
- same for their own reads: an empty answer then says nothing was *read*, never that nothing
229
- exists, and a World Cup day names the fixtures shown from the bundled schedule without
230
- their live state (with no "Live data" line when none shown was served). Off the World Cup a day
231
- the provider could not be asked for says so, never "no matches scheduled".
232
- No clipboard tool? `claudinho share … | pbcopy` works too.
233
-
234
- ### Want an image?
235
-
236
- The snippet is plain text, so a screenshot *is* your share card, or render one with an
237
- existing tool, e.g. `freeze --execute "claudinho share next MEX" -o card.png`
238
- (charmbracelet/freeze), `silicon`, or carbon.now.sh. Claudinho stays text-first:
239
- no bundled image renderer, no fonts or licensing to worry about.
240
-
241
- ## Statusline (Claude Code)
274
+ | `--style <social\|compact>` | `social` (default) is the full card; `compact` is one line per match |
275
+ | `--copy` | also copy the card to the clipboard |
276
+ | `--no-hashtag` | leave out the `#VibingLaVidaLoca` tag |
277
+ | `--no-install-line` | leave out the `Try it: …` line |
278
+
279
+ `share … --json` prints the card (`kind`, `snippet`) with its facts. A card for a day, live
280
+ matches, a match or a team's next match adds `matches`, `marketSignals` and `marketComplete`; a
281
+ table card adds `tables`, and a bracket card the bracket's `view`. When the market read did not
282
+ finish, the card says so and `--json` carries `marketComplete: false`: that is not "no market".
283
+
284
+ Want an image? A screenshot of the card works, or a tool such as
285
+ `freeze --execute "claudinho share next" -o card.png` (charmbracelet/freeze).
286
+
287
+ ## Prediction-market odds (World Cup)
288
+
289
+ For World Cup matches, `claudinho markets` shows a "who's favored" line: the percentages Polymarket's
290
+ public market data implies, for a date, a match, or a team's current match (one whose kickoff time
291
+ passed less than about three and a half hours ago and that the feed has not marked finished) or,
292
+ with none, its next. A short version appears under `today`,
293
+ `match` and on the share cards for a date, a match or a team's next match, when a reliable market
294
+ exists. **Informational only, not betting
295
+ advice:** no trading, no links. A match with no clean market, and a finished match, shows nothing.
296
+ With `--json`, `markets` carries `complete`, and `today` and `match` carry `marketComplete`. `false`
297
+ means the optional market read did not finish, and the text says so: it is not "no market".
298
+ Turn the short version off with `--no-markets` or `CLAUDINHO_MARKETS=off`; `claudinho markets`
299
+ still answers. The statusline and the hook never show market data.
300
+
301
+ ## Statusline: Claude Code
242
302
 
243
303
  ```bash
244
- claudinho init-statusline # patches ~/.claude/settings.json (backs up first)
304
+ claudinho init-statusline # edits ~/.claude/settings.json (backed up first)
245
305
  claudinho init-statusline --print # just print the snippet
246
306
  ```
247
307
 
248
- The statusline reads from a local micro-cache and **never blocks on the
249
- network** (<150ms). When several matches are live it shows them all inline:
250
- `⚽ 🇳🇴 1–1 🇫🇷 87' · 🇸🇳 1–2 🇮🇶 86'`. Customize via env:
308
+ The statusline reads a local cache and never waits on the network. A background process refreshes
309
+ the cache. When several matches are live it shows up to eight side by side, and counts the rest
310
+ (`+N`): `⚽ ARS 2–1 CHE 67' · LIV 0–0 MNC 12'`. When your team is in play it shows that match
311
+ alone and counts the others.
312
+ With nothing in play it counts down to the next match: `⚽ ARS vs LEE in 2d4h`. From kickoff until the data provider
313
+ marks the match live, for up to 30 minutes, it reads `⚽ BET vs BAR now` instead of moving on to the next fixture.
314
+ The statusline is English in every language.
251
315
 
252
- - `CLAUDINHO_TEAM=MEX`: your team's match first, the others counted (`+N`) (a nation name works too, e.g. `CLAUDINHO_TEAM=mexico`); also the default team for `next`, `markets next`, and `share next` when the argument is omitted. A team pinned with `claudinho follow <alias> --team <name>` does the same when this is not set, under its own competition (a pin is scoped to its competition, whatever chose it): a preference, never a filter
253
- - `CLAUDINHO_MAX=2`: cap how many live matches show inline (rest collapse to exact `+N`
254
- after a complete cache scan, or `+more` when the bounded scan cannot know the count; default: 8,
255
- and values above 8 are capped at 8)
256
- - `CLAUDINHO_COMPACT=0`: show 3-letter codes alongside flags
257
- - `CLAUDINHO_FLAGS=off`: drop emoji flags for 3-letter codes (statusline) / plain names (`today`, `live`, `table`, `next`, hook); already automatic on terminals that can't render flag emoji, e.g. Warp
316
+ - `CLAUDINHO_TEAM=ARS`: that team's match first, the others counted (`+N`). Give a code; on the World Cup a nation's name works too. A pinned team does the same when this is not set.
317
+ - `CLAUDINHO_MAX=2`: show at most this many live matches when your team is not in play (default and maximum: 8); the rest are counted.
318
+ - `CLAUDINHO_COMPACT=0`: show 3-letter codes beside the flags.
319
+ - `CLAUDINHO_FLAGS=off`: no flag emoji: 3-letter codes on the statusline and in `bracket`, plain names in `today`, `live`, `next`, `match`, `table`, `team` and the hook (automatic in Warp, which cannot draw them). Share cards keep their flags. `CLAUDINHO_FLAGS=on` keeps the flags.
258
320
 
259
- A background refresher writes that cache. Whenever no cache could be read (none, unreadable, or rejected), its attempts are paced by a small attempt record beside the throttle note, both named by scope like the cache itself (`attempt*.json`, `backoff*.json`: bare for the World Cup, `attempt.json`; with the source and competition for another, `attempt.espn.eng.1.json` for the Premier League): after each attempt the next waits one minute, the wait doubling to at most thirty, and only an attempt whose own cache then reads back resets it (a cache repaired by hand does not, so if it disappears again the retained wait applies). A rewrite repairs most of what can be at the cache path (a corrupted cache, a link, a file or a pipe whose own mode lets its owner read it, a file refused by an access-control list or owned by another user included; not a file marked immutable or append-only, a flag the refresher cannot see, which it treats exactly as it would without the flag: one that cannot be read as a cache and is not among the entries named next as beyond a rewrite has its attempts paced while the attempt record works, and repeated as before the record existed when it does not; one that reads as a cache never involves the record, so the refresher fetches whenever an update is due and never replaces it, as before); it cannot repair a directory, or anything but a link whose own mode denies its owner a read (a file or a pipe at mode 000, say: a rewrite keeps those mode bits), unless the directory's own inherited permissions let a new file at those bits be read, which is measured, not assumed. There, if the attempt record cannot be written or read either, the refresher asks nothing while the statusline keeps starting one on every update, until a rewrite could repair the cache path (the entry removed, its owner-read bit set, or the directory's inherited permissions letting a new file at those bits be read) or the attempt record works again; a cache file that merely opens is not enough (a mode-000 file with its own allow-read access-control entry opens, and a rewrite keeps its bits, not its entry). The same holds in a cache directory whose new files nobody can read (an inherited deny-read access-control list): no attempt asks or publishes anything (the refresher cannot read back its own lock), and the statusline keeps starting one on every update, until files in that directory can be read again.
321
+ The same `claudinho prompt` works in tmux (`set -g status-right '#(claudinho prompt)'`) and as a
322
+ Starship custom command.
260
323
 
261
- Use the same `claudinho prompt` in **tmux** (`set -g status-right '#(claudinho prompt)'`)
262
- or a **Starship** custom command: it works in any shell.
324
+ Running a local build (a checkout, no global `claudinho`)? `init-statusline`, `init-hook` and
325
+ `init-cursor-statusline` take `--command`. From the checkout's root, after a build:
326
+ `node packages/cli/dist/index.js init-cursor-statusline --command "node \"$PWD/packages/cli/dist/index.js\" prompt"`.
263
327
 
264
- ### Score-aware Claude (hook)
328
+ ### Score-aware Claude: the hook
265
329
 
266
330
  ```bash
267
- claudinho init-hook # patches ~/.claude/settings.json (backs up first)
331
+ claudinho init-hook # edits ~/.claude/settings.json (backed up first)
268
332
  ```
269
333
 
270
- Wires `claudinho hook` into Claude Code's `UserPromptSubmit`. During a match,
271
- the live score is injected into Claude's context so it can mention it naturally;
272
- off-match it's silent (zero added tokens). Restart Claude Code to activate.
334
+ The hook runs on every prompt you send in Claude Code. During a match it adds the live score to
335
+ what Claude reads, so Claude can mention it. Off-match it adds nothing. Restart Claude Code to
336
+ activate it.
337
+
338
+ **Moving to the Claude Code plugin?** The plugin draws the score above the prompt and adds the
339
+ score to your prompts itself. Run `claudinho init plugin` first: it removes the statusline whose
340
+ command is `claudinho prompt` and the hook whose command is `claudinho hook` (the commands
341
+ `init claude`, `init-statusline` and `init-hook` write by default), and keeps every other setting.
342
+ Any other command stays (a wrapper, an `npx` form, a `--command` one); `init plugin` names it when
343
+ it mentions `claudinho`. Then see the
344
+ [plugin's README](https://github.com/arturogarrido/claudinho/tree/main/packages/plugin#readme).
273
345
 
274
- ## Statusline (Cursor CLI)
346
+ ## Statusline: Cursor CLI
347
+
348
+ Cursor CLI is Cursor's agent in the terminal.
275
349
 
276
350
  ```bash
277
- claudinho init-cursor-statusline # patches ~/.cursor/cli-config.json (backs up first)
351
+ claudinho init-cursor-statusline # edits ~/.cursor/cli-config.json (backed up first)
278
352
  claudinho init-cursor-statusline --print # just print the snippet
279
353
  ```
280
354
 
281
- Uses the same `claudinho prompt` hot path as Claude Code, so the same
282
- `CLAUDINHO_TEAM` / `CLAUDINHO_MAX` / `CLAUDINHO_COMPACT` customizations above apply
283
- here too. Cursor-specific tuning is applied automatically (`updateIntervalMs: 1000`,
284
- `timeoutMs: 1500`).
355
+ It runs the same `claudinho prompt`, so the settings above apply. `export CLAUDINHO_CURSOR_META=auto`
356
+ adds a second line with the model, the context in use, the worktree and the vim mode. Cursor's
357
+ prompt hook cannot pass the score to the model yet, so the score-aware hook is Claude Code only.
285
358
 
286
- Optional second line with session meta (model, context %, worktree, vim mode) **below** the score:
359
+ ## For a program: `claudinho ambient --json`
287
360
 
288
- ```bash
289
- export CLAUDINHO_CURSOR_META=auto # recommended for Cursor CLI
290
- ```
361
+ `claudinho ambient --json [--columns N]` prints the statusline and the hook as one JSON object on
362
+ one line, for a program that draws them itself (the Claude Code plugin does). It reads the local
363
+ cache, never the network (it may start the background refresh, as the statusline does), and exits 0
364
+ whatever the cache holds.
291
365
 
292
- Custom command (local dev or monorepo checkout):
366
+ | Field | What it holds |
367
+ |---|---|
368
+ | `line` | the line `claudinho prompt` prints, but in the CLI's language (`--lang`; `prompt` itself stays English), fitted to `--columns N` with its `+N` count kept. Its `+more`, the World Cup's sign-off and `⚽ —` stay English, and the countdown's units (`2d4h`, `17m`) are the same in every language |
369
+ | `lineParts` | the same line in parts, each shown score marked `strong`; their texts joined equal `line` |
370
+ | `idle` | `true` on the end-of-edition line, however narrow the fit |
371
+ | `empty` | `true` when nothing is known |
372
+ | `context` | the hook's block, or `null` |
373
+ | `live` | the live matches, each with `picked`, `pinned`, `pinnedSide` (`home`, `away` or `null`: your saved team's side, never `CLAUDINHO_TEAM`'s), `names` (the two names the hook's line uses), `lastGoal` (the last goal, only when the provider's goals add up to the score, none listed twice, the credited side having scored; else `null`) and `line` (the hook's own line for it) |
374
+ | `finished` | the matches the refresher saw in play and then read at full time, each for 10 minutes from that read, while the snapshot's last live read was whole; each with `pinnedSide`, `names` and a `line` ending `(FT)` |
375
+ | `current` | whether `live` is a current, whole read: an empty list means nothing is on only when this is `true` |
376
+ | `next` | the fixture the countdown names (a kickoff that passed less than 30 minutes ago, still listed as scheduled, included) |
377
+ | `pick`, `competition` | the team preferred and the competition followed |
378
+ | `lang`, `words` | the CLI's resolved language, and the words of the plugin's goal and full-time moments in it |
379
+ | `degraded`, `source`, `updatedAt`, `staleAfter` | where the data came from, when, and when its live scores stop being shown |
380
+ | `disclaimer`, `attribution` | the non-affiliation sentence, and the provider's (`Live data: ESPN`; `null` with no snapshot, on the end-of-edition line and when nothing is known) |
381
+
382
+ Read `idle` and `empty`, never the text of `line`, to decide what the line is. With nothing chosen
383
+ it prints `{ "competition": null, "noCompetition": true }` and the first-run line.
293
384
 
294
- ```bash
295
- claudinho init-cursor-statusline --command "node ./packages/cli/dist/index.js prompt"
296
- ```
385
+ ## How it works
297
386
 
298
- > Cursor's `beforeSubmitPrompt` hook does not yet reliably inject live-score
299
- > context into the model. Use `init-hook` for Claude Code; Cursor CLI is
300
- > statusline-only until hook injection lands.
387
+ The World Cup's schedule (104 matches, with venues and kickoffs) ships with the package, so it works
388
+ offline. Every other competition's schedule, and every live score, comes from ESPN's public
389
+ scoreboard (an answer from it says `Live data: ESPN`). World Cup market odds come from
390
+ Polymarket's public data. Rate limits are respected, and the CLI backs off when a provider asks it
391
+ to.
301
392
 
302
- ## How it works
393
+ ## Privacy
303
394
 
304
- The World Cup's full fixture list (104 matches, groups, venues, host cities, kickoffs) ships
305
- **bundled** in the package, so its schedule is offline and instant; the other competitions'
306
- schedules are read from the feed, so `today` and `next` need the network there. Live match
307
- state always hits the network. Live scores come from **ESPN's** public scoreboard (a
308
- swappable provider, attributed in output as `Live data: ESPN`) and market signals
309
- from Polymarket; provider attribution and rate limits are respected.
310
-
311
- ## Privacy Policy
312
-
313
- No personal data collected: no accounts, no telemetry, no analytics, no tracking, and no
314
- Claudinho server. To show live results, the CLI makes read-only requests to public services
315
- (ESPN; Polymarket for informational-only market signals) with no account or personal data
316
- attached, though those services still receive standard request metadata (such as your IP
317
- address) like any HTTP call. It keeps a small cache of public match data in your local cache
318
- directory (`~/.cache/claudinho`, or `$XDG_CACHE_HOME/claudinho`), and the optional `init`
319
- commands update your Claude Code / Cursor settings file after saving a one-time
320
- `.claudinho.bak` backup, all on your machine, never uploaded. Full policy:
395
+ No accounts, no telemetry, no analytics, no tracking, and no Claudinho server. The CLI asks ESPN
396
+ (and Polymarket, for World Cup odds) for public data with nothing about you attached; those services
397
+ still see standard request metadata, such as your IP address, like any web request. It keeps your
398
+ choice and a cache of public match data on your machine (see [Where it is saved](#where-it-is-saved)).
399
+ The `init` commands edit only the settings file they name, after a
400
+ one-time `.claudinho.bak` backup. Nothing is uploaded. Full policy:
321
401
  [PRIVACY.md](https://github.com/arturogarrido/claudinho/blob/main/PRIVACY.md).
322
402
 
323
403
  ## License
324
404
 
325
- MIT © 2026 Arturo Garrido · [source & issues](https://github.com/arturogarrido/claudinho)
326
-
327
- > **Not affiliated with FIFA, any confederation, league or club, or Anthropic. Nor endorsed by or connected to any of them.** An independent,
328
- > open-source fan project showing factual match data (scores, fixtures, standings) with emoji
329
- > flags only: no logos, crests, kits, broadcast footage, or player likenesses.
405
+ MIT © 2026 Arturo Garrido · [source and issues](https://github.com/arturogarrido/claudinho) ·
406
+ like it? [star it on GitHub](https://github.com/arturogarrido/claudinho) (`claudinho star` shows how).
330
407
 
331
- ---
408
+ Independent fan project. Not affiliated with FIFA, any confederation, league or club, or Anthropic.
332
409
 
333
410
  _Built while watching the games._ **#VibingLaVidaLoca** ⚽