claudial 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -45
- package/dist/snapshot.js +3 -2
- package/dist/statusline/cache.js +2 -1
- package/dist/statusline/line.js +7 -6
- package/dist/ui/MatchRow.js +5 -4
- package/dist/ui/TakeoverView.js +3 -2
- package/dist/ui/Ticker.js +3 -2
- package/dist/ui/UpcomingSection.js +3 -2
- package/dist/ui/flags.js +43 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,15 +32,15 @@ your local time below. Auto-refreshing while you work.
|
|
|
32
32
|
▝▜█████▛▘ ───────────────────────
|
|
33
33
|
|
|
34
34
|
⏺ LIVE 67'
|
|
35
|
-
|
|
35
|
+
ARG 🇦🇷 2 — 1 🇲🇽 MEX
|
|
36
36
|
⚽ 23' Messi · 51' Álvarez | 44' Lozano
|
|
37
37
|
|
|
38
38
|
⏺ LIVE 12'
|
|
39
|
-
|
|
39
|
+
FRA 🇫🇷 0 — 0 🇸🇳 SEN
|
|
40
40
|
|
|
41
41
|
○ UPCOMING
|
|
42
|
-
18:00
|
|
43
|
-
21:00
|
|
42
|
+
18:00 BRA 🇧🇷 — 🇭🇷 CRO
|
|
43
|
+
21:00 ESP 🇪🇸 — 🇲🇦 MAR
|
|
44
44
|
|
|
45
45
|
r refresh · q quit
|
|
46
46
|
```
|
|
@@ -57,7 +57,7 @@ takes over:
|
|
|
57
57
|
|
|
58
58
|
LIONEL MESSI · 23'
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
ARG 🇦🇷 2 — 1 🇲🇽 MEX
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
```
|
|
@@ -66,55 +66,26 @@ Four seconds of glory, then back to the board.
|
|
|
66
66
|
|
|
67
67
|
## Claude Code, World Cup edition
|
|
68
68
|
|
|
69
|
-
The whole point
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
Needs [tmux](https://github.com/tmux/tmux) (`sudo pacman -S tmux` /
|
|
74
|
-
`sudo apt install tmux` / `brew install tmux`), then:
|
|
69
|
+
The whole point: your Claude Code, score-aware. One command and the live score
|
|
70
|
+
sits right in Claude Code's status bar — under the input box, always visible
|
|
71
|
+
while Claude works, with nothing to wire up by hand.
|
|
75
72
|
|
|
76
73
|
```bash
|
|
77
74
|
npm install -g claudial
|
|
78
|
-
|
|
79
|
-
alias claude-mundial="tmux new-session 'claude' \; split-window -v -l 5 'claudial --ticker' \; select-pane -U"
|
|
80
|
-
EOF
|
|
75
|
+
claudial setup
|
|
81
76
|
```
|
|
82
77
|
|
|
83
|
-
|
|
78
|
+
The wizard drops a live-score statusline into your Claude Code settings:
|
|
84
79
|
|
|
85
80
|
```
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
│ ⏺ Working… │
|
|
90
|
-
│ │
|
|
91
|
-
├──────────────────────────────────────┤
|
|
92
|
-
│ ⏺ 67' QAT 0—1 SUI │
|
|
93
|
-
│ ○ BRA—MOR Sun 01:00 │
|
|
94
|
-
└──────────────────────────────────────┘
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
When a goal goes in, the strip lights up while Claude keeps working.
|
|
98
|
-
(`bash` users: append to `~/.bashrc` instead. Prefer the full board beside
|
|
99
|
-
Claude rather than a strip below? Use
|
|
100
|
-
`split-window -h -l 44 'claudial'` in the alias.)
|
|
101
|
-
|
|
102
|
-
Don't want to hand-edit your shell config? `claudial setup --tmux` appends the
|
|
103
|
-
right alias for you — zsh, bash or fish — idempotently.
|
|
104
|
-
|
|
105
|
-
Want the score *inside* Claude Code itself — in its status bar, no tmux at
|
|
106
|
-
all? Install everything in one step:
|
|
107
|
-
|
|
108
|
-
```bash
|
|
109
|
-
npm install -g claudial
|
|
110
|
-
claudial setup
|
|
81
|
+
⚽ QAT 🇶🇦 0—1 🇨🇭 SUI 67' · main ← during a match
|
|
82
|
+
○ QAT 🇶🇦 — 🇨🇭 SUI 10:00 PM · main ← between matches (next kickoff, or last result)
|
|
83
|
+
⚽ G O O O L · ARG 🇦🇷 1—0 🇲🇽 MEX ← for 15s whenever anyone scores
|
|
111
84
|
```
|
|
112
85
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
is pure Node — no `jq`/`flock`/`curl`, so it runs on macOS and Windows too.
|
|
117
|
-
Non-interactive: `claudial setup --statusline --global --tmux --shell zsh --yes`.
|
|
86
|
+
It refreshes every few seconds and is pure Node, so it runs on macOS and
|
|
87
|
+
Windows too, in any shell. Restart Claude Code after setup to see it.
|
|
88
|
+
Scriptable, no prompts: `claudial setup --statusline --global --yes`.
|
|
118
89
|
|
|
119
90
|
## Why a TUI
|
|
120
91
|
|
package/dist/snapshot.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { fetchLive, fetchRecent, fetchUpcoming } from './api/espn.js';
|
|
2
2
|
import { formatKickoff } from './ui/UpcomingSection.js';
|
|
3
|
+
import { homeTag, awayTag } from './ui/flags.js';
|
|
3
4
|
function line(m) {
|
|
4
5
|
if (m.status === 'upcoming')
|
|
5
|
-
return `o ${formatKickoff(m.startTimestamp).padEnd(13)} ${m.home.
|
|
6
|
+
return `o ${formatKickoff(m.startTimestamp).padEnd(13)} ${homeTag(m.home.code)} - ${awayTag(m.away.code)}`;
|
|
6
7
|
const label = m.status === 'finished' ? 'FT' : m.status === 'halftime' ? 'HT' : `${m.minute ?? '?'}'`;
|
|
7
|
-
return `* ${label.padEnd(4)} ${m.home.
|
|
8
|
+
return `* ${label.padEnd(4)} ${homeTag(m.home.code)} ${m.homeScore ?? '-'} - ${m.awayScore ?? '-'} ${awayTag(m.away.code)}`;
|
|
8
9
|
}
|
|
9
10
|
export async function printSnapshot(seasonId) {
|
|
10
11
|
const [live, upcoming, recent] = await Promise.all([
|
package/dist/statusline/cache.js
CHANGED
|
@@ -1,6 +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
5
|
export const TTL_MS = 10_000;
|
|
5
6
|
export const GOAL_WINDOW_MS = 15_000;
|
|
6
7
|
const LOCK_STALE_MS = 30_000;
|
|
@@ -8,7 +9,7 @@ const LOCK_STALE_MS = 30_000;
|
|
|
8
9
|
const GOAL_OPEN = '\x1b[1;38;2;217;119;87m';
|
|
9
10
|
const GOAL_CLOSE = '\x1b[0m';
|
|
10
11
|
function goalText(m) {
|
|
11
|
-
return `${GOAL_OPEN}⚽ G O O O L · ${m.home.code} ${m.homeScore ?? 0}—${m.awayScore ?? 0} ${m.away.code}${GOAL_CLOSE}`;
|
|
12
|
+
return `${GOAL_OPEN}⚽ G O O O L · ${homeTag(m.home.code)} ${m.homeScore ?? 0}—${m.awayScore ?? 0} ${awayTag(m.away.code)}${GOAL_CLOSE}`;
|
|
12
13
|
}
|
|
13
14
|
export function makeCache(dir = join(tmpdir(), 'claudial-statusline')) {
|
|
14
15
|
mkdirSync(dir, { recursive: true });
|
package/dist/statusline/line.js
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import { formatKickoff } from '../ui/UpcomingSection.js';
|
|
2
|
+
import { homeTag, awayTag } from '../ui/flags.js';
|
|
2
3
|
// Assumed wall-clock length of a match (kickoff → ~full time). ESPN exposes no
|
|
3
4
|
// end-time, so a finished match is anchored at kickoff + this for proximity math.
|
|
4
5
|
const MATCH_MS = 2 * 60 * 60 * 1000;
|
|
5
|
-
/** A live/halftime match: "⚽ QAT 0—1 SUI 67'" (
|
|
6
|
+
/** A live/halftime match: "⚽ QAT 🇶🇦 0—1 🇨🇭 SUI 67'" (mirrored code+flag). */
|
|
6
7
|
export function liveLine(match) {
|
|
7
8
|
const clock = match.status === 'halftime' ? ' HT'
|
|
8
9
|
: match.minute != null ? ` ${match.minute}'`
|
|
9
10
|
: '';
|
|
10
|
-
return `⚽ ${match.home.code} ${match.homeScore ?? 0}—${match.awayScore ?? 0} ${match.away.code}${clock}`;
|
|
11
|
+
return `⚽ ${homeTag(match.home.code)} ${match.homeScore ?? 0}—${match.awayScore ?? 0} ${awayTag(match.away.code)}${clock}`;
|
|
11
12
|
}
|
|
12
|
-
/** A finished match with its result: "⚽ QAT 2—1 SUI FT". */
|
|
13
|
+
/** A finished match with its result: "⚽ QAT 🇶🇦 2—1 🇨🇭 SUI FT". */
|
|
13
14
|
export function finishedLine(match) {
|
|
14
|
-
return `⚽ ${match.home.code} ${match.homeScore ?? 0}—${match.awayScore ?? 0} ${match.away.code} FT`;
|
|
15
|
+
return `⚽ ${homeTag(match.home.code)} ${match.homeScore ?? 0}—${match.awayScore ?? 0} ${awayTag(match.away.code)} FT`;
|
|
15
16
|
}
|
|
16
|
-
/** The next kickoff: "○ BRA—
|
|
17
|
+
/** The next kickoff: "○ BRA 🇧🇷 — 🇲🇦 MAR Sun 01:00". */
|
|
17
18
|
function upcomingLine(match, now) {
|
|
18
|
-
return `○ ${match.home.code}
|
|
19
|
+
return `○ ${homeTag(match.home.code)} — ${awayTag(match.away.code)} ${formatKickoff(match.startTimestamp, now)}`;
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
22
|
* Live matches joined by two spaces. Otherwise show whichever moment is closer
|
package/dist/ui/MatchRow.js
CHANGED
|
@@ -2,6 +2,7 @@ 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
4
|
import { formatKickoff } from './UpcomingSection.js';
|
|
5
|
+
import { homeTag, awayTag } from './flags.js';
|
|
5
6
|
export function ScorerLine({ incidents }) {
|
|
6
7
|
const goals = incidents.filter((i) => i.kind === 'goal');
|
|
7
8
|
if (goals.length === 0)
|
|
@@ -32,12 +33,12 @@ function statusLabel(m) {
|
|
|
32
33
|
export function MatchRow({ m, incidents, compact }) {
|
|
33
34
|
if (m.status === 'upcoming') {
|
|
34
35
|
if (compact) {
|
|
35
|
-
return _jsxs(Text, { dimColor: true, children: ["\u25CB ", m.home.code, "\u2014", m.away.code, " ", formatKickoff(m.startTimestamp)] });
|
|
36
|
+
return _jsxs(Text, { dimColor: true, children: ["\u25CB ", homeTag(m.home.code), " \u2014 ", awayTag(m.away.code), " ", formatKickoff(m.startTimestamp)] });
|
|
36
37
|
}
|
|
37
|
-
return (_jsxs(Text, { children: [_jsxs(Text, { dimColor: true, children: ["\u25CB ", formatKickoff(m.startTimestamp).padEnd(13)] }), m.home.
|
|
38
|
+
return (_jsxs(Text, { children: [_jsxs(Text, { dimColor: true, children: ["\u25CB ", formatKickoff(m.startTimestamp).padEnd(13)] }), homeTag(m.home.code), " \u2014 ", awayTag(m.away.code), m.group ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", m.group] }) : null] }));
|
|
38
39
|
}
|
|
39
40
|
if (compact) {
|
|
40
|
-
return (_jsxs(Text, { children: [_jsx(Text, { color: m.status === 'finished' ? undefined : ACCENT, children: "\u23FA" }), _jsxs(Text, { dimColor: true, children: [" ", statusLabel(m), " "] }), m.home.code, " ", _jsxs(Text, { bold: true, color: ACCENT, children: [m.homeScore ?? '–', "\u2014", m.awayScore ?? '–'] }), " ", m.away.code, m.varInProgress ? _jsx(Text, { dimColor: true, children: " \u2696" }) : null] }));
|
|
41
|
+
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), " ", _jsxs(Text, { bold: true, color: ACCENT, children: [m.homeScore ?? '–', "\u2014", m.awayScore ?? '–'] }), " ", awayTag(m.away.code), m.varInProgress ? _jsx(Text, { dimColor: true, children: " \u2696" }) : null] }));
|
|
41
42
|
}
|
|
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: [' ', m.home.
|
|
43
|
+
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), " ", _jsxs(Text, { bold: true, color: ACCENT, children: [m.homeScore ?? '–', " \u2014 ", m.awayScore ?? '–'] }), " ", awayTag(m.away.code)] }), _jsx(ScorerLine, { incidents: incidents }), _jsx(FeedLine, { incidents: incidents })] }));
|
|
43
44
|
}
|
package/dist/ui/TakeoverView.js
CHANGED
|
@@ -2,6 +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
6
|
const ART = {
|
|
6
7
|
goal: { art: GOAL_ART, color: ACCENT, caption: null },
|
|
7
8
|
redcard: { art: RED_CARD_ART, color: RED, caption: 'R E D C A R D' },
|
|
@@ -16,7 +17,7 @@ export function TakeoverView({ takeover }) {
|
|
|
16
17
|
const rows = process.stdout.rows ?? 24;
|
|
17
18
|
if (rows < 14) {
|
|
18
19
|
const label = kind === 'goal' ? 'G O A L' : kind === 'redcard' ? 'R E D C A R D' : 'V A R';
|
|
19
|
-
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: [match.home.
|
|
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), " "] }), _jsxs(Text, { bold: true, color: ACCENT, children: [takeover.homeScore, " \u2014 ", takeover.awayScore] }), _jsxs(Text, { dimColor: true, children: [" ", awayTag(match.away.code)] })] })] }));
|
|
20
21
|
}
|
|
21
|
-
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: [match.home.
|
|
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), " "] }), _jsxs(Text, { bold: true, color: ACCENT, children: [takeover.homeScore, " \u2014 ", takeover.awayScore] }), _jsxs(Text, { dimColor: true, children: [" ", awayTag(match.away.code)] })] })] }));
|
|
22
23
|
}
|
package/dist/ui/Ticker.js
CHANGED
|
@@ -3,13 +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
7
|
function TickerTakeover({ t }) {
|
|
7
8
|
const color = t.kind === 'redcard' ? RED : ACCENT;
|
|
8
9
|
const label = t.kind === 'goal' ? 'G O A L' : t.kind === 'redcard' ? 'R E D' : 'V A R';
|
|
9
|
-
return (_jsxs(Text, { bold: true, color: color, wrap: "truncate", children: [label, " \u00B7 ", t.who ? spacedCaps(t.who) : t.detail ?? '', " \u00B7 ", t.match.home.code, " ", t.homeScore, "\u2014", t.awayScore, " ", t.match.away.code] }));
|
|
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.homeScore, "\u2014", t.awayScore, " ", awayTag(t.match.away.code)] }));
|
|
10
11
|
}
|
|
11
12
|
export function Ticker({ live, upcoming, takeover }) {
|
|
12
13
|
if (takeover)
|
|
13
14
|
return _jsx(TickerTakeover, { t: takeover });
|
|
14
|
-
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' : '', " "] }), m.home.code, " ", _jsxs(Text, { bold: true, color: ACCENT, children: [m.homeScore ?? '–', "\u2014", m.awayScore ?? '–'] }), " ", m.away.code, m.varInProgress ? _jsx(Text, { dimColor: true, children: " \u2696" }) : null] }, m.id))), upcoming[0] ? (_jsxs(Text, { dimColor: true, wrap: "truncate", children: ["\u25CB ", upcoming[0].home.code, "\u2014", upcoming[0].away.code, " ", 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' : '', " "] }), homeTag(m.home.code), " ", _jsxs(Text, { bold: true, color: ACCENT, children: [m.homeScore ?? '–', "\u2014", m.awayScore ?? '–'] }), " ", awayTag(m.away.code), 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), " \u2014 ", awayTag(upcoming[0].away.code), " ", formatKickoff(upcoming[0].startTimestamp)] })) : null] }));
|
|
15
16
|
}
|
|
@@ -1,5 +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
4
|
export function formatKickoff(ts, now = Date.now()) {
|
|
4
5
|
const d = new Date(ts * 1000);
|
|
5
6
|
const time = d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
|
|
@@ -15,7 +16,7 @@ export function UpcomingSection({ matches, compact }) {
|
|
|
15
16
|
return null;
|
|
16
17
|
if (compact) {
|
|
17
18
|
const m = shown[0];
|
|
18
|
-
return (_jsxs(Text, { dimColor: true, children: ["\u25CB ", m.home.code, "\u2014", m.away.code, " ", formatKickoff(m.startTimestamp)] }));
|
|
19
|
+
return (_jsxs(Text, { dimColor: true, children: ["\u25CB ", homeTag(m.home.code), " \u2014 ", awayTag(m.away.code), " ", formatKickoff(m.startTimestamp)] }));
|
|
19
20
|
}
|
|
20
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "UPCOMING" }), shown.map((m) => (_jsxs(Text, { children: [_jsxs(Text, { dimColor: true, children: ["\u25CB ", formatKickoff(m.startTimestamp).padEnd(13)] }), m.home.
|
|
21
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "UPCOMING" }), shown.map((m) => (_jsxs(Text, { children: [_jsxs(Text, { dimColor: true, children: ["\u25CB ", formatKickoff(m.startTimestamp).padEnd(13)] }), homeTag(m.home.code), " \u2014 ", awayTag(m.away.code), m.group ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", m.group] }) : null] }, m.id)))] }));
|
|
21
22
|
}
|
package/dist/ui/flags.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Country flag emoji for a team, keyed on ESPN's 3-letter abbreviation
|
|
2
|
+
// (verified against the live FIFA World Cup feed — note ESPN uses MAR for
|
|
3
|
+
// Morocco). Bracket placeholders (1A, QFW1, RD16 W1, …) have no flag.
|
|
4
|
+
// ESPN abbreviation → ISO 3166-1 alpha-2.
|
|
5
|
+
const ISO2 = {
|
|
6
|
+
ALG: 'DZ', ARG: 'AR', AUS: 'AU', AUT: 'AT', BEL: 'BE', BIH: 'BA', BRA: 'BR',
|
|
7
|
+
CAN: 'CA', CIV: 'CI', COD: 'CD', COL: 'CO', CPV: 'CV', CRO: 'HR', CUW: 'CW',
|
|
8
|
+
CZE: 'CZ', ECU: 'EC', EGY: 'EG', ESP: 'ES', FRA: 'FR', GER: 'DE', GHA: 'GH',
|
|
9
|
+
HAI: 'HT', IRN: 'IR', IRQ: 'IQ', JOR: 'JO', JPN: 'JP', KOR: 'KR', KSA: 'SA',
|
|
10
|
+
MAR: 'MA', MEX: 'MX', NED: 'NL', NOR: 'NO', NZL: 'NZ', PAN: 'PA', PAR: 'PY',
|
|
11
|
+
POR: 'PT', QAT: 'QA', RSA: 'ZA', SEN: 'SN', SUI: 'CH', SWE: 'SE', TUN: 'TN',
|
|
12
|
+
TUR: 'TR', URU: 'UY', USA: 'US', UZB: 'UZ',
|
|
13
|
+
};
|
|
14
|
+
// Home nations have no ISO2 flag — use the subdivision tag sequence.
|
|
15
|
+
const SUBDIVISION = {
|
|
16
|
+
ENG: 'gbeng', SCO: 'gbsct', WAL: 'gbwls',
|
|
17
|
+
};
|
|
18
|
+
function regionalIndicator(iso2) {
|
|
19
|
+
return String.fromCodePoint(...[...iso2].map((c) => 0x1f1e6 + (c.charCodeAt(0) - 65)));
|
|
20
|
+
}
|
|
21
|
+
function subdivisionFlag(sub) {
|
|
22
|
+
return String.fromCodePoint(0x1f3f4, ...[...sub].map((c) => 0xe0000 + c.charCodeAt(0)), 0xe007f);
|
|
23
|
+
}
|
|
24
|
+
/** Flag emoji for a team code, or '' if unknown (e.g. bracket placeholders). */
|
|
25
|
+
export function flagFor(code) {
|
|
26
|
+
const iso2 = ISO2[code];
|
|
27
|
+
if (iso2)
|
|
28
|
+
return regionalIndicator(iso2);
|
|
29
|
+
const sub = SUBDIVISION[code];
|
|
30
|
+
if (sub)
|
|
31
|
+
return subdivisionFlag(sub);
|
|
32
|
+
return '';
|
|
33
|
+
}
|
|
34
|
+
/** Home side: "QAT 🇶🇦" (flag trails the code), or just the code if unmapped. */
|
|
35
|
+
export function homeTag(code) {
|
|
36
|
+
const f = flagFor(code);
|
|
37
|
+
return f ? `${code} ${f}` : code;
|
|
38
|
+
}
|
|
39
|
+
/** Away side: "🇨🇭 SUI" (flag leads the code), or just the code if unmapped. */
|
|
40
|
+
export function awayTag(code) {
|
|
41
|
+
const f = flagFor(code);
|
|
42
|
+
return f ? `${f} ${code}` : code;
|
|
43
|
+
}
|