claudial 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,7 +23,7 @@ npm install -g claudial && claudial setup
23
23
 
24
24
  <br/>
25
25
 
26
- ![claudial: a live football board in the terminal, taken over by a full-screen GOAL](dashboard.gif)
26
+ ![claudial: Liverpool 6–0 Manchester United live, then a full-screen GOAL for Firmino's 88th-minute seventh](dashboard.gif)
27
27
 
28
28
  **Premier League · La Liga · Serie A · Bundesliga · Ligue 1 · Champions League ·
29
29
  Europa League · Conference League · Super League Greece · World Cup**
@@ -32,6 +32,20 @@ Europa League · Conference League · Super League Greece · World Cup**
32
32
 
33
33
  ---
34
34
 
35
+ ## Get started
36
+
37
+ | You are… | Run |
38
+ |---|---|
39
+ | **New to claudial** | `npm install -g claudial`<br>`claudial setup` |
40
+ | **Back from the World Cup** (you have 0.6.x) | `npm install -g claudial@latest`<br>`claudial --version` (should print 0.7.0 or newer)<br>`claudial follow` |
41
+
42
+ Then restart Claude Code. World Cup users keep their status line as it is; `claudial follow`
43
+ picks your leagues and teams, because the new version starts following nothing.
44
+
45
+ Still seeing "WORLD CUP 2026" after updating? An old copy comes first on your PATH.
46
+ `which -a claudial` lists every copy; remove the old one with
47
+ `npm uninstall -g --prefix <its folder> claudial` (e.g. `--prefix ~/.npm-global`).
48
+
35
49
  ## The score, right under your prompt
36
50
 
37
51
  One command puts a live score line into Claude Code's status bar. It stays out
@@ -39,15 +53,15 @@ of the way while Claude works, and lights up the moment anyone scores:
39
53
 
40
54
  <div align="center">
41
55
 
42
- ![claudial: live scores in Claude Code's status bar, flashing GOOOL on a goal](demo.gif)
56
+ ![claudial: Liverpool 7–0 Manchester United flashing GOOOL in Claude Code's status bar](demo.gif)
43
57
 
44
58
  </div>
45
59
 
46
60
  ```
47
- ⚽ ARS 1—1 LIV 77' ⚽ PAO 1—0 OLY 67' ⚽ RMA 2—2 BAR HT · main ← every match you follow
48
- ⚽ G O O O L · ARS 2—1 LIV · main ← for 15 s after a goal
49
- 🟥 R E D · VAN DIJK · ARS — LIV · main ← for 15 s after a red card
50
- ○ FCB — PSG Sun 07:00 PM · main ← between matches: next kickoff
61
+ ⚽ Liverpool 6—0 Manchester United 87' · main ← every match you follow
62
+ ⚽ G O O O L · Liverpool 7—0 Manchester United · main ← for 15 s after a goal
63
+ 🟥 R E D · CASEMIRO · Manchester United — Southampton · main ← for 15 s after a red card
64
+ ○ Liverpool — Arsenal Sat 05:30 PM · main ← between matches: next kickoff
51
65
  ```
52
66
 
53
67
  Your git branch stays at the end. **Already run a custom status line?** Setup
@@ -62,11 +76,11 @@ esc to save:
62
76
  ```
63
77
  claudial · what do you follow?
64
78
 
65
- search › pana▏
79
+ search › liver▏
66
80
 
67
- › ◉ Panathinaikos PAO · Super League Greece
81
+ › ◉ Liverpool LIV · Premier League
68
82
 
69
- Following Panathinaikos · Arsenal
83
+ Following Liverpool
70
84
  ↑↓ move · space follow · type to search · esc clear search · ctrl+c cancel
71
85
  ```
72
86
 
@@ -159,4 +173,8 @@ npm test # vitest
159
173
  [Request a feature](https://github.com/lefProg/claudial/issues) ·
160
174
  [npm](https://www.npmjs.com/package/claudial)
161
175
 
176
+ <br/>
177
+
178
+ **YNWA** 🔴
179
+
162
180
  </div>
@@ -110,7 +110,7 @@ export async function fetchLiveRedCards() {
110
110
  continue;
111
111
  for (const inc of sm.incidents) {
112
112
  if (inc.kind === 'redCard') {
113
- out.push({ id: inc.id, homeCode: m.home.code, awayCode: m.away.code, player: inc.playerShort ?? inc.player ?? '?', minute: inc.minute, national: m.home.national !== false });
113
+ out.push({ id: inc.id, homeCode: m.home.code, awayCode: m.away.code, homeName: m.home.name, awayName: m.away.name, player: inc.playerShort ?? inc.player ?? '?', minute: inc.minute, national: m.home.national !== false });
114
114
  }
115
115
  }
116
116
  }
package/dist/index.js CHANGED
@@ -79,7 +79,7 @@ async function main() {
79
79
  }
80
80
  if (args.includes('--mock-redcard')) {
81
81
  const { makeCache } = await import('./statusline/cache.js');
82
- makeCache().armRedCard({ player: 'Otamendi', homeCode: 'ARG', awayCode: 'ENG' });
82
+ makeCache().armRedCard({ player: 'Otamendi', homeCode: 'ARG', awayCode: 'ENG', homeName: 'Argentina', awayName: 'England' });
83
83
  process.stdout.write('🟥 RED armed — the Claude Code status bar flashes a red card (ARG—ENG) for 15s.\n');
84
84
  process.exit(0);
85
85
  }
package/dist/snapshot.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { fetchLive, fetchRecent, fetchUpcoming, followsNothing } from './api/backend.js';
2
- import { formatKickoff } from './ui/UpcomingSection.js';
3
- import { homeTag, awayTag } from './ui/flags.js';
2
+ import { kickoffColumn } from './ui/UpcomingSection.js';
3
+ import { homeName, awayName } from './ui/flags.js';
4
4
  function line(m) {
5
5
  if (m.status === 'upcoming')
6
- return `o ${formatKickoff(m.startTimestamp).padEnd(13)} ${homeTag(m.home.code, m.home.national)} - ${awayTag(m.away.code, m.away.national)}`;
6
+ return `o ${kickoffColumn(m.startTimestamp)}${homeName(m.home)} - ${awayName(m.away)}`;
7
7
  const label = m.status === 'finished' ? 'FT' : m.status === 'halftime' ? 'HT' : `${m.minute ?? '?'}'`;
8
- return `* ${label.padEnd(4)} ${homeTag(m.home.code, m.home.national)} ${m.homeScore ?? '-'} - ${m.awayScore ?? '-'} ${awayTag(m.away.code, m.away.national)}`;
8
+ return `* ${label.padEnd(4)} ${homeName(m.home)} ${m.homeScore ?? '-'} - ${m.awayScore ?? '-'} ${awayName(m.away)}`;
9
9
  }
10
10
  export async function printSnapshot(seasonId) {
11
11
  const [live, upcoming, recent] = await Promise.all([
@@ -1,7 +1,7 @@
1
1
  import { mkdirSync, openSync, closeSync, writeFileSync, readFileSync, renameSync, statSync, existsSync, unlinkSync, utimesSync, futimesSync, } from 'node:fs';
2
2
  import { tmpdir } from 'node:os';
3
3
  import { join } from 'node:path';
4
- import { homeTag, awayTag } from '../ui/flags.js';
4
+ import { homeName, awayName } from '../ui/flags.js';
5
5
  export const TTL_MS = 10_000;
6
6
  export const GOAL_WINDOW_MS = 15_000;
7
7
  const LOCK_STALE_MS = 30_000;
@@ -11,10 +11,12 @@ const GOAL_CLOSE = '\x1b[0m';
11
11
  // RED #E5484D — the red-card flash.
12
12
  const RED_OPEN = '\x1b[1;38;2;229;72;77m';
13
13
  function goalText(m) {
14
- return `${GOAL_OPEN}⚽ G O O O L · ${homeTag(m.home.code, m.home.national)} ${m.homeScore ?? 0}—${m.awayScore ?? 0} ${awayTag(m.away.code, m.away.national)}${GOAL_CLOSE}`;
14
+ return `${GOAL_OPEN}⚽ G O O O L · ${homeName(m.home)} ${m.homeScore ?? 0}—${m.awayScore ?? 0} ${awayName(m.away)}${GOAL_CLOSE}`;
15
15
  }
16
16
  function redCardText(rc) {
17
- return `${RED_OPEN}🟥 R E D · ${rc.player.toUpperCase()} · ${homeTag(rc.homeCode, rc.national)} — ${awayTag(rc.awayCode, rc.national)}${GOAL_CLOSE}`;
17
+ const home = homeName({ name: rc.homeName ?? rc.homeCode, code: rc.homeCode, national: rc.national });
18
+ const away = awayName({ name: rc.awayName ?? rc.awayCode, code: rc.awayCode, national: rc.national });
19
+ return `${RED_OPEN}🟥 R E D · ${rc.player.toUpperCase()} · ${home} — ${away}${GOAL_CLOSE}`;
18
20
  }
19
21
  export function makeCache(dir = join(tmpdir(), 'claudial-statusline')) {
20
22
  mkdirSync(dir, { recursive: true });
@@ -1,5 +1,5 @@
1
1
  import { formatKickoff } from '../ui/UpcomingSection.js';
2
- import { homeTag, awayTag } from '../ui/flags.js';
2
+ import { homeName, awayName } from '../ui/flags.js';
3
3
  import { matchPrediction } from '../predictions/match.js';
4
4
  import { headline } from '../predictions/format.js';
5
5
  // Assumed wall-clock length of a match (kickoff → ~full time). ESPN exposes no
@@ -10,18 +10,18 @@ export function liveLine(match) {
10
10
  const clock = match.status === 'halftime' ? ' HT'
11
11
  : match.minute != null ? ` ${match.minute}'`
12
12
  : '';
13
- return `⚽ ${homeTag(match.home.code, match.home.national)} ${match.homeScore ?? 0}—${match.awayScore ?? 0} ${awayTag(match.away.code, match.away.national)}${clock}`;
13
+ return `⚽ ${homeName(match.home)} ${match.homeScore ?? 0}—${match.awayScore ?? 0} ${awayName(match.away)}${clock}`;
14
14
  }
15
15
  /** A finished match with its result: "⚽ QAT 🇶🇦 2—1 🇨🇭 SUI FT". */
16
16
  export function finishedLine(match) {
17
- return `⚽ ${homeTag(match.home.code, match.home.national)} ${match.homeScore ?? 0}—${match.awayScore ?? 0} ${awayTag(match.away.code, match.away.national)} FT`;
17
+ return `⚽ ${homeName(match.home)} ${match.homeScore ?? 0}—${match.awayScore ?? 0} ${awayName(match.away)} FT`;
18
18
  }
19
19
  /**
20
20
  * The next kickoff: "○ BRA 🇧🇷 — 🇲🇦 MAR Sun 01:00", with a prediction headline
21
21
  * appended when one is available: "… · 🔮 BRA win 64% · 2-1".
22
22
  */
23
23
  function upcomingLine(match, now, preds) {
24
- const base = `○ ${homeTag(match.home.code, match.home.national)} — ${awayTag(match.away.code, match.away.national)} ${formatKickoff(match.startTimestamp, now)}`;
24
+ const base = `○ ${homeName(match.home)} — ${awayName(match.away)} ${formatKickoff(match.startTimestamp, now)}`;
25
25
  const pred = matchPrediction(match, preds);
26
26
  return pred ? `${base} · ${headline(pred)}` : base;
27
27
  }
@@ -1,15 +1,23 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
3
  import { ACCENT } from './Header.js';
4
- import { formatKickoff } from './UpcomingSection.js';
5
- import { homeTag, awayTag } from './flags.js';
4
+ import { formatKickoff, kickoffColumn } from './UpcomingSection.js';
5
+ import { homeName, awayName } from './flags.js';
6
+ /** One side's goals grouped by scorer, in order of their first goal: "43' 50' C. Gakpo · 47' D. Núñez". */
7
+ export function scorers(goals) {
8
+ const byPlayer = new Map();
9
+ for (const g of goals) {
10
+ const who = g.playerShort ?? '?';
11
+ byPlayer.set(who, [...(byPlayer.get(who) ?? []), `${g.minute ?? '?'}'`]);
12
+ }
13
+ return [...byPlayer].map(([who, mins]) => `${mins.join(' ')} ${who}`).join(' · ');
14
+ }
6
15
  export function ScorerLine({ incidents }) {
7
16
  const goals = incidents.filter((i) => i.kind === 'goal');
8
17
  if (goals.length === 0)
9
18
  return null;
10
- const fmt = (g) => `${g.minute ?? '?'}' ${g.playerShort ?? '?'}`;
11
- const home = goals.filter((g) => g.isHome).map(fmt).join(' · ');
12
- const away = goals.filter((g) => !g.isHome).map(fmt).join(' · ');
19
+ const home = scorers(goals.filter((g) => g.isHome));
20
+ const away = scorers(goals.filter((g) => !g.isHome));
13
21
  return _jsxs(Text, { dimColor: true, children: [' ', "\u26BD ", [home, away].filter(Boolean).join(' | ')] });
14
22
  }
15
23
  export function FeedLine({ incidents }) {
@@ -32,12 +40,12 @@ function statusLabel(m) {
32
40
  export function MatchRow({ m, incidents, compact }) {
33
41
  if (m.status === 'upcoming') {
34
42
  if (compact) {
35
- return _jsxs(Text, { dimColor: true, children: ["\u25CB ", homeTag(m.home.code, m.home.national), " \u2014 ", awayTag(m.away.code, m.away.national), " ", formatKickoff(m.startTimestamp)] });
43
+ return _jsxs(Text, { dimColor: true, children: ["\u25CB ", homeName(m.home), " \u2014 ", awayName(m.away), " ", formatKickoff(m.startTimestamp)] });
36
44
  }
37
- return (_jsxs(Text, { children: [_jsxs(Text, { dimColor: true, children: ["\u25CB ", formatKickoff(m.startTimestamp).padEnd(13)] }), homeTag(m.home.code, m.home.national), " \u2014 ", awayTag(m.away.code, m.away.national), m.group ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", m.group] }) : null] }));
45
+ return (_jsxs(Text, { children: [_jsxs(Text, { dimColor: true, children: ["\u25CB ", kickoffColumn(m.startTimestamp)] }), homeName(m.home), " \u2014 ", awayName(m.away), m.group ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", m.group] }) : null] }));
38
46
  }
39
47
  if (compact) {
40
- return (_jsxs(Text, { children: [_jsx(Text, { color: m.status === 'finished' ? undefined : ACCENT, children: "\u23FA" }), _jsxs(Text, { dimColor: true, children: [" ", statusLabel(m), " "] }), homeTag(m.home.code, m.home.national), " ", _jsxs(Text, { bold: true, color: ACCENT, children: [m.homeScore ?? '–', "\u2014", m.awayScore ?? '–'] }), " ", awayTag(m.away.code, m.away.national), m.varInProgress ? _jsx(Text, { dimColor: true, children: " \u2696" }) : null] }));
48
+ return (_jsxs(Text, { children: [_jsx(Text, { color: m.status === 'finished' ? undefined : ACCENT, children: "\u23FA" }), _jsxs(Text, { dimColor: true, children: [" ", statusLabel(m), " "] }), homeName(m.home), " ", _jsxs(Text, { bold: true, color: ACCENT, children: [m.homeScore ?? '–', "\u2014", m.awayScore ?? '–'] }), " ", awayName(m.away), m.varInProgress ? _jsx(Text, { dimColor: true, children: " \u2696" }) : null] }));
41
49
  }
42
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: m.status === 'finished' ? undefined : ACCENT, children: "\u23FA" }), _jsxs(Text, { bold: m.status !== 'finished', dimColor: m.status === 'finished', children: [" ", statusLabel(m)] }), m.group ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", m.group] }) : null, m.varInProgress ? _jsx(Text, { dimColor: true, children: " \u00B7 \u2696 VAR" }) : null] }), _jsxs(Text, { children: [' ', homeTag(m.home.code, m.home.national), " ", _jsxs(Text, { bold: true, color: ACCENT, children: [m.homeScore ?? '–', " \u2014 ", m.awayScore ?? '–'] }), " ", awayTag(m.away.code, m.away.national)] }), _jsx(ScorerLine, { incidents: incidents }), _jsx(FeedLine, { incidents: incidents })] }));
50
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: m.status === 'finished' ? undefined : ACCENT, children: "\u23FA" }), _jsxs(Text, { bold: m.status !== 'finished', dimColor: m.status === 'finished', children: [" ", statusLabel(m)] }), m.group ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", m.group] }) : null, m.varInProgress ? _jsx(Text, { dimColor: true, children: " \u00B7 \u2696 VAR" }) : null] }), _jsxs(Text, { children: [' ', homeName(m.home), " ", _jsxs(Text, { bold: true, color: ACCENT, children: [m.homeScore ?? '–', " \u2014 ", m.awayScore ?? '–'] }), " ", awayName(m.away)] }), _jsx(ScorerLine, { incidents: incidents }), _jsx(FeedLine, { incidents: incidents })] }));
43
51
  }
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
3
  import { GOAL_ART, VAR_ART, RED_CARD_ART, spacedCaps } from '../banner.js';
4
4
  import { ACCENT, RED } from './Header.js';
5
- import { homeTag, awayTag } from './flags.js';
5
+ import { homeName, awayName } from './flags.js';
6
6
  const ART = {
7
7
  goal: { art: GOAL_ART, color: ACCENT, caption: null },
8
8
  redcard: { art: RED_CARD_ART, color: RED, caption: 'R E D C A R D' },
@@ -17,7 +17,7 @@ export function TakeoverView({ takeover }) {
17
17
  const rows = process.stdout.rows ?? 24;
18
18
  if (rows < 14) {
19
19
  const label = kind === 'goal' ? 'G O A L' : kind === 'redcard' ? 'R E D C A R D' : 'V A R';
20
- return (_jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", height: rows - 1, children: [_jsx(Text, { bold: true, color: color, wrap: "truncate", children: label }), _jsxs(Text, { bold: true, wrap: "truncate", children: [spacedCaps(fallback), takeover.minute != null ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", takeover.minute, "'"] }) : null] }), takeover.detail ? _jsx(Text, { dimColor: true, wrap: "truncate", children: takeover.detail }) : null, _jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { dimColor: true, children: [homeTag(match.home.code, match.home.national), " "] }), _jsxs(Text, { bold: true, color: ACCENT, children: [takeover.homeScore, " \u2014 ", takeover.awayScore] }), _jsxs(Text, { dimColor: true, children: [" ", awayTag(match.away.code, match.away.national)] })] })] }));
20
+ return (_jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", height: rows - 1, children: [_jsx(Text, { bold: true, color: color, wrap: "truncate", children: label }), _jsxs(Text, { bold: true, wrap: "truncate", children: [spacedCaps(fallback), takeover.minute != null ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", takeover.minute, "'"] }) : null] }), takeover.detail ? _jsx(Text, { dimColor: true, wrap: "truncate", children: takeover.detail }) : null, _jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: "gray", children: [homeName(match.home), " "] }), _jsxs(Text, { bold: true, color: ACCENT, children: [takeover.homeScore, " \u2014 ", takeover.awayScore] }), _jsxs(Text, { color: "gray", children: [" ", awayName(match.away)] })] })] }));
21
21
  }
22
- return (_jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", height: rows - 1, children: [art.map((line, i) => (_jsx(Text, { bold: true, color: color, wrap: "truncate", children: line }, i))), caption ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { bold: true, color: color, wrap: "truncate", children: caption }) })) : null, _jsx(Box, { marginTop: 1, children: _jsxs(Text, { bold: true, children: [spacedCaps(fallback), takeover.minute != null ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", takeover.minute, "'"] }) : null] }) }), takeover.detail ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: takeover.detail }) })) : null, _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { dimColor: true, children: [homeTag(match.home.code, match.home.national), " "] }), _jsxs(Text, { bold: true, color: ACCENT, children: [takeover.homeScore, " \u2014 ", takeover.awayScore] }), _jsxs(Text, { dimColor: true, children: [" ", awayTag(match.away.code, match.away.national)] })] })] }));
22
+ return (_jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", height: rows - 1, children: [art.map((line, i) => (_jsx(Text, { bold: true, color: color, wrap: "truncate", children: line }, i))), caption ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { bold: true, color: color, wrap: "truncate", children: caption }) })) : null, _jsx(Box, { marginTop: 1, children: _jsxs(Text, { bold: true, children: [spacedCaps(fallback), takeover.minute != null ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", takeover.minute, "'"] }) : null] }) }), takeover.detail ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: takeover.detail }) })) : null, _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: "gray", children: [homeName(match.home), " "] }), _jsxs(Text, { bold: true, color: ACCENT, children: [takeover.homeScore, " \u2014 ", takeover.awayScore] }), _jsxs(Text, { color: "gray", children: [" ", awayName(match.away)] })] })] }));
23
23
  }
package/dist/ui/Ticker.js CHANGED
@@ -3,14 +3,14 @@ import { Box, Text } from 'ink';
3
3
  import { formatKickoff } from './UpcomingSection.js';
4
4
  import { ACCENT, RED } from './Header.js';
5
5
  import { spacedCaps } from '../banner.js';
6
- import { homeTag, awayTag } from './flags.js';
6
+ import { homeName, awayName } from './flags.js';
7
7
  function TickerTakeover({ t }) {
8
8
  const color = t.kind === 'redcard' ? RED : ACCENT;
9
9
  const label = t.kind === 'goal' ? 'G O A L' : t.kind === 'redcard' ? 'R E D' : 'V A R';
10
- return (_jsxs(Text, { bold: true, color: color, wrap: "truncate", children: [label, " \u00B7 ", t.who ? spacedCaps(t.who) : t.detail ?? '', " \u00B7 ", homeTag(t.match.home.code, t.match.home.national), " ", t.homeScore, "\u2014", t.awayScore, " ", awayTag(t.match.away.code, t.match.away.national)] }));
10
+ return (_jsxs(Text, { bold: true, color: color, wrap: "truncate", children: [label, " \u00B7 ", t.who ? spacedCaps(t.who) : t.detail ?? '', " \u00B7 ", homeName(t.match.home), " ", t.homeScore, "\u2014", t.awayScore, " ", awayName(t.match.away)] }));
11
11
  }
12
12
  export function Ticker({ live, upcoming, takeover }) {
13
13
  if (takeover)
14
14
  return _jsx(TickerTakeover, { t: takeover });
15
- return (_jsxs(Box, { flexDirection: "column", children: [live.slice(0, 3).map((m) => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: ACCENT, children: "\u23FA" }), _jsxs(Text, { dimColor: true, children: [" ", m.minute != null ? `${m.minute}'` : m.status === 'halftime' ? 'HT' : m.status === 'finished' ? 'FT' : '', " "] }), homeTag(m.home.code, m.home.national), " ", _jsxs(Text, { bold: true, color: ACCENT, children: [m.homeScore ?? '–', "\u2014", m.awayScore ?? '–'] }), " ", awayTag(m.away.code, m.away.national), m.varInProgress ? _jsx(Text, { dimColor: true, children: " \u2696" }) : null] }, m.id))), upcoming[0] ? (_jsxs(Text, { dimColor: true, wrap: "truncate", children: ["\u25CB ", homeTag(upcoming[0].home.code, upcoming[0].home.national), " \u2014 ", awayTag(upcoming[0].away.code, upcoming[0].away.national), " ", formatKickoff(upcoming[0].startTimestamp)] })) : null] }));
15
+ return (_jsxs(Box, { flexDirection: "column", children: [live.slice(0, 3).map((m) => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: ACCENT, children: "\u23FA" }), _jsxs(Text, { dimColor: true, children: [" ", m.minute != null ? `${m.minute}'` : m.status === 'halftime' ? 'HT' : m.status === 'finished' ? 'FT' : '', " "] }), homeName(m.home), " ", _jsxs(Text, { bold: true, color: ACCENT, children: [m.homeScore ?? '–', "\u2014", m.awayScore ?? '–'] }), " ", awayName(m.away), m.varInProgress ? _jsx(Text, { dimColor: true, children: " \u2696" }) : null] }, m.id))), upcoming[0] ? (_jsxs(Text, { dimColor: true, wrap: "truncate", children: ["\u25CB ", homeName(upcoming[0].home), " \u2014 ", awayName(upcoming[0].away), " ", formatKickoff(upcoming[0].startTimestamp)] })) : null] }));
16
16
  }
@@ -1,6 +1,6 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
- import { homeTag, awayTag } from './flags.js';
3
+ import { homeName, awayName } from './flags.js';
4
4
  import { matchPrediction } from '../predictions/match.js';
5
5
  import { fullSlate, headline } from '../predictions/format.js';
6
6
  export function formatKickoff(ts, now = Date.now()) {
@@ -14,19 +14,26 @@ export function formatKickoff(ts, now = Date.now()) {
14
14
  const day = d.toLocaleDateString(undefined, soon ? { weekday: 'short' } : { weekday: 'short', day: 'numeric', month: 'short' });
15
15
  return `${day} ${time}`;
16
16
  }
17
+ /** The kickoff padded to `width` (default: fits the longest date format), always followed by a space. */
18
+ export function kickoffColumn(ts, now = Date.now(), width = 22) {
19
+ const k = formatKickoff(ts, now);
20
+ return k.length >= width - 1 ? `${k} ` : k.padEnd(width);
21
+ }
17
22
  export function UpcomingSection({ matches, compact, predictions = [], }) {
18
23
  const shown = matches.slice(0, compact ? 1 : 8);
19
24
  if (shown.length === 0)
20
25
  return null;
26
+ // One column for the whole list: as wide as its longest kickoff, plus a gap.
27
+ const width = Math.max(...shown.map((m) => formatKickoff(m.startTimestamp).length)) + 2;
21
28
  if (compact) {
22
29
  const m = shown[0];
23
30
  const pred = matchPrediction(m, predictions);
24
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["\u25CB ", homeTag(m.home.code, m.home.national), " \u2014 ", awayTag(m.away.code, m.away.national), " ", formatKickoff(m.startTimestamp)] }), pred ? _jsx(Text, { color: "yellow", children: headline(pred) }) : null] }));
31
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["\u25CB ", homeName(m.home), " \u2014 ", awayName(m.away), " ", formatKickoff(m.startTimestamp)] }), pred ? _jsx(Text, { color: "yellow", children: headline(pred) }) : null] }));
25
32
  }
26
33
  // Every fixture shows its pick; the NEXT one (shown[0]) gets the full slate.
27
34
  return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "UPCOMING" }), shown.map((m, i) => {
28
35
  const pred = matchPrediction(m, predictions);
29
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsxs(Text, { dimColor: true, children: ["\u25CB ", formatKickoff(m.startTimestamp).padEnd(13)] }), homeTag(m.home.code, m.home.national), " \u2014 ", awayTag(m.away.code, m.away.national), m.group ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", m.group] }) : null] }), pred && i === 0
36
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsxs(Text, { dimColor: true, children: ["\u25CB ", kickoffColumn(m.startTimestamp, Date.now(), width)] }), homeName(m.home), " \u2014 ", awayName(m.away), m.group ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", m.group] }) : null] }), pred && i === 0
30
37
  ? fullSlate(pred).map((ln, j) => _jsx(Text, { color: "yellow", children: ln }, j))
31
38
  : pred
32
39
  ? _jsxs(Text, { color: "yellow", children: [" ", headline(pred)] })
package/dist/ui/flags.js CHANGED
@@ -45,3 +45,13 @@ export function awayTag(code, national = true) {
45
45
  const f = national ? flagFor(code) : '';
46
46
  return f ? `${f} ${code}` : code;
47
47
  }
48
+ /** Home side by full name, for the roomy views: "Qatar 🇶🇦", or "Arsenal" for a club. */
49
+ export function homeName(t) {
50
+ const f = t.national !== false ? flagFor(t.code) : '';
51
+ return f ? `${t.name} ${f}` : t.name;
52
+ }
53
+ /** Away side by full name: "🇨🇭 Switzerland", or "Liverpool" for a club. */
54
+ export function awayName(t) {
55
+ const f = t.national !== false ? flagFor(t.code) : '';
56
+ return f ? `${f} ${t.name}` : t.name;
57
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudial",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Live football scores for your leagues and teams, in your terminal and Claude Code's status bar — goal and red-card takeovers included",
5
5
  "type": "module",
6
6
  "bin": {