Touchline is a Bubble Tea terminal application for live FIFA World Cup scores, match details, and group standings.
Touchline keeps terminal UI, service orchestration, provider access, and domain types separate:
cmd/touchlineis the composition root. It wires config, logging, providers, caches, services, and the Bubble Tea program.internal/typescontains provider-neutral football domain types.internal/apidefines provider interfaces, the live ESPN provider, and a mock provider.internal/servicesowns cache-aware application use cases.internal/tuiowns Bubble Tea state transitions, rendering, and Lip Gloss styles.internal/cacheprovides a small TTL cache used by services.internal/configloads.envand environment variables.mockcontains local JSON responses for development without API keys.
The main architectural decision is that the TUI never calls a concrete API client. It talks to services, and services depend on interfaces. That keeps the code ready for multiple providers, competitions, SSH mode with Wish, WebSocket updates, player statistics, timelines, xG, and win probability without rewriting rendering logic.
By default Touchline pulls live FIFA World Cup data from ESPN's public soccer API. The provider is date-aware: pressing left/right changes the day and fetches that day's scoreboard from .../soccer/fifa.world/scoreboard?dates=YYYYMMDD. A single request per day returns the full picture, so match details (statistics plus a goal/card timeline with scorers and minutes) are bundled with the match list and cached together.
- Matches, scores, status, and per-match details/statistics come from ESPN.
- Group standings come from ESPN's standings endpoint.
Set TOUCHLINE_PROVIDER=mock to run fully offline against the bundled JSON.
q: quitr: refresh current viewleft/rightorh/l: move the match list one day backward or forwardtab: switch between the dashboard and group standingsup/downork/j: select a match (its details show in the right pane) or scroll
go mod tidy
go run ./cmd/touchlineTo build a binary:
go build -o bin/touchline ./cmd/touchline
./bin/touchlineTouchline can be served over SSH using charmbracelet/wish. Each connection gets its own Bubble Tea program (independent view state), while the data services and caches are shared across all sessions so concurrent viewers reuse the same upstream data.
TOUCHLINE_SSH=true go run ./cmd/touchlineThen, from another terminal:
ssh -p 23234 localhostA host key is generated automatically on first run at TOUCHLINE_SSH_HOST_KEY_PATH (default .ssh/touchline_ed25519). Connections require an interactive terminal (enforced by Wish's activeterm middleware); colors are forced on so SSH clients get the full themed UI.
Copy .env.example to .env and adjust values as needed.
cp .env.example .envSupported variables:
TOUCHLINE_PROVIDER: provider name.espn(default, live) ormock(offline).TOUCHLINE_COMPETITION: competition code. Currentlyworld-cup.TOUCHLINE_MOCK_DIR: mock JSON directory.TOUCHLINE_ESPN_BASE_URL: optional override for the ESPN scoreboard base URL.TOUCHLINE_REFRESH_INTERVAL: auto-refresh interval, for example30s.TOUCHLINE_CACHE_TTL: in-memory cache TTL, for example25s.TOUCHLINE_LOG_LEVEL:debug,info,warn, orerror.TOUCHLINE_SSH: set totrueto serve the TUI over SSH instead of running locally.TOUCHLINE_SSH_ADDR: SSH listen address, for examplelocalhost:23234.TOUCHLINE_SSH_HOST_KEY_PATH: host key path (auto-generated if missing).
Mock responses live in:
mock/live_matches.jsonmock/match_details.jsonmock/standings.json
These files let the full TUI run locally without network access or API keys.
The live ESPN provider (internal/api/espn.go) is a complete reference implementation. To add a different source:
- Add a new provider type under
internal/api. - Implement
FootballProvider:
type FootballProvider interface {
GetScoreboard(ctx context.Context, competition types.CompetitionCode, date time.Time) (types.Scoreboard, error)
GetStandings(ctx context.Context, competition types.CompetitionCode) ([]types.GroupStanding, error)
}GetScoreboard returns a types.Scoreboard containing the day's Matches plus a Details map keyed by match ID, so matches and their statistics/timeline are fetched together.
- Use
net/httpinside the provider and map provider-specific payloads intointernal/types. - Register the provider in
buildProviderincmd/touchline/main.go. - Add
.envvariables for the API base URL and API key.
Keep provider DTOs private to internal/api so provider changes do not leak into the TUI or services.