No description
  • TypeScript 95%
  • JavaScript 2.8%
  • CSS 2.1%
  • HTML 0.1%
Find a file
Joey Davis 70c70ca980 Untrack .kiro/ (Kiro IDE workspace) and gitignore it
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 22:55:41 -05:00
scripts Initial commit: read-only League companion (champ-select helper + in-game overlay) 2026-06-23 22:34:06 -05:00
src Initial commit: read-only League companion (champ-select helper + in-game overlay) 2026-06-23 22:34:06 -05:00
test Initial commit: read-only League companion (champ-select helper + in-game overlay) 2026-06-23 22:34:06 -05:00
.env.example Add .env.example template for RIOT_API_KEY 2026-06-23 22:54:51 -05:00
.gitattributes Add .gitattributes to normalize line endings (LF) 2026-06-23 22:49:31 -05:00
.gitignore Untrack .kiro/ (Kiro IDE workspace) and gitignore it 2026-06-23 22:55:41 -05:00
package-lock.json Initial commit: read-only League companion (champ-select helper + in-game overlay) 2026-06-23 22:34:06 -05:00
package.json Initial commit: read-only League companion (champ-select helper + in-game overlay) 2026-06-23 22:34:06 -05:00
README.md Add .env.example template for RIOT_API_KEY 2026-06-23 22:54:51 -05:00
tsconfig.json Initial commit: read-only League companion (champ-select helper + in-game overlay) 2026-06-23 22:34:06 -05:00
tsconfig.main.json Initial commit: read-only League companion (champ-select helper + in-game overlay) 2026-06-23 22:34:06 -05:00
vite.config.ts Initial commit: read-only League companion (champ-select helper + in-game overlay) 2026-06-23 22:34:06 -05:00
vitest.config.ts Initial commit: read-only League companion (champ-select helper + in-game overlay) 2026-06-23 22:34:06 -05:00
vitest.workspace.ts Initial commit: read-only League companion (champ-select helper + in-game overlay) 2026-06-23 22:34:06 -05:00

LoL Companion

A read-only League of Legends companion — ban/pick help during champ select, and a live in-game overlay. It only reads the game; it never hovers, bans, locks, or sends anything.

Features

  • Defensive Bans — champions to ban that hard-counter your teammates' hovered picks.
  • Suggested Picks — champions you could pick that beat your locked lane opponent, drawn from your champion pool and/or the current meta tier list.
  • In-game overlay — a transparent, click-through window showing your live CS/min vs. your tier's average and your gold difference vs. your lane opponent.

It runs as Electron windows and reads Riot's official local APIs — the LCU (champ select) and Live Client Data (in-game). Read-only is enforced at the connection boundary: only HTTP GET and WebSocket subscribe are allowed; everything state-changing is rejected.

Quick start

npm install
npm start

Requires Node 18+ (20 LTS recommended). The League client doesn't need to be running to launch — the app waits for it and shows "Waiting for League client."

Optional — Riot API key (for the overlay's tier-average comparison only). Copy the template and add your key:

cp .env.example .env
RIOT_API_KEY=RGAPI-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Get one at https://developer.riotgames.com/ (a Personal key is permanent; the dev key expires every 24h). .env is git-ignored. Without a key, the overlay still shows your CS/min and gold difference — just no comparison target.

Overlay display mode: set League to Borderless (or Fullscreen with Windows "Fullscreen Optimizations" on — the Win10/11 default). True exclusive fullscreen bypasses the Windows compositor, so no separate-window overlay can draw over it.


How it works

The Electron main process runs a few components; the React renderer is a thin presentation layer that subscribes to main-process events over a typed contextBridge.

  • LCU Connector — discovers the League lockfile, authenticates, and subscribes to champ-select/gameflow events (read-only).
  • Session Tracker — extracts the live champ-select model (your role, ally hovers, bans, locked picks).
  • Stats Provider — fetches counter-rating data from a third-party source (op.gg, NA Gold+) for the current patch, with on-disk caching, rate limiting, retries, and stale-cache fallback.
  • Recommender — pure functions that rank ban targets by a threat score and counter-picks by a sample-weighted pick score.
  • Live poller — while a game is running, reads the Live Client Data API once per second and drives the overlay.
  • Harvest trigger — when you lock a champion, kicks off a background benchmark harvest for it (skip-if-fresh) so its tier-average is ready next time.

Suggested Picks reuse the opponent's counters page read in reverse: a champion that strongly counters your lane opponent sits near the top of their counters list. So the app fetches one page — your locked opponent's counters at your role — and ranks the champions that beat them, intersected with your pool / the meta list. Riot doesn't expose enemy hovers, so these appear once the enemy laner locks in.

Configuration

The Champ pool & tiers panel (bottom of the window) edits the two settings you tune most — your per-lane champion pool and which op.gg tiers feed the "Top meta" list — live, without a restart. Edits save to settings.json immediately.

settings.json lives in Electron's per-user data directory (hand-editable):

  • Windows: %APPDATA%\champ-select-assistant\settings.json
  • macOS: ~/Library/Application Support/champ-select-assistant/settings.json
  • Linux: ~/.config/champ-select-assistant/settings.json

Any missing/invalid field falls back to its default. The stats cache sits alongside as stats-cache.json.

Field Range Default Description
statsSource u-gg | op-gg | lolalytics op-gg Third-party stats source adapter
cacheTtlHours 1168 168 How long cached stats stay fresh
threatMatchupThreshold -1.00.0 0 Counter rating at/below which an opponent counts as a "threat." 0 admits every counter past the source's game-count floor; lower toward -1 to require stronger counters
rateLimitPerSecond 160 1 Max requests/sec to the stats source
myPool per-role name lists {} Your champion pool for Suggested Picks, keyed by role (TOP/JUNGLE/MIDDLE/BOTTOM/UTILITY). Unrecognized names are skipped
metaTierFilter op.gg buckets 05 (0 = OP) [0, 1] Which tiers the "Top meta" list draws from
{
  "statsSource": "op-gg",
  "cacheTtlHours": 168,
  "threatMatchupThreshold": 0,
  "rateLimitPerSecond": 1,
  "myPool": {
    "JUNGLE": ["Briar", "Lee Sin", "Vi"],
    "MIDDLE": ["Ahri", "Sylas"]
  },
  "metaTierFilter": [0, 1]
}

myPool and metaTierFilter are optional — omit myPool and Suggested Picks fall back to the meta list for your role.

Benchmark data (overlay tier averages)

The overlay's "tier average" numbers come from an offline harvest of Riot's Match-V5 API, stored per (champion, role, tier) in src/shared/benchmarks/benchmarks.json. Riot has no "matches for champion X" endpoint, so the harvester pulls whole ranked games at a tier and records every participant — filling many champions' data at once.

Automatic: locking a champion starts a background harvest for it (at the compare tier, currently GOLD) unless its data is already fresh. A harvest takes minutes, so the data lands after that game and is used on the next launch. Requires RIOT_API_KEY.

Manual:

npm run harvest:benchmarks -- --champion Briar --tier GOLD
npm run harvest:benchmarks -- --championId 233 --tier GOLD --targetSamples 200

Runs are rate-limited, checkpointed, and resumable (Ctrl+C and re-run). Output is src/shared/benchmarks/benchmarks.json.

Development
npm run dev        # Vite dev server + Electron, hot-reloading UI (Ctrl+C stops both)
npm start          # clean rebuild, then launch against dist/
npm test           # all suites (unit, property, integration)
npm run typecheck  # tsc --noEmit

npm start always clean-rebuilds first, so a stale build is impossible. Editing main-process code (src/main, src/preload) needs a npm run dev restart — only the renderer hot-reloads.

Windows gotcha: if ELECTRON_RUN_AS_NODE is set in your environment (even to ""), npm start crashes with Cannot read properties of undefined (reading 'isPackaged') — Electron runs as plain Node instead of launching the GUI. Remove it (PowerShell: Remove-Item Env:\ELECTRON_RUN_AS_NODE); emptying it is not enough.

Script What it does
npm run dev Dev server + Electron, hot-reloading UI
npm run build Build main + renderer into dist/
npm start Clean rebuild, then launch
npm test All test suites
npm run typecheck Type-check without emitting
npm run harvest:benchmarks Harvest overlay tier-average data
npm run gen:tags Regenerate champion tags from Data Dragon
npm run gen:item-costs Regenerate item costs from Data Dragon

The test suite includes property-based tests (read-only contract, session extraction, stats cache, retry/fallback, threat scoring, list invariants) plus integration tests against a mock LCU and mock stats source.

src/
  main/       Electron main process (LCU, session, stats, recommender, live, pipeline, IPC)
  preload/    contextBridge (typed, read-only message contract)
  renderer/   React companion window + in-game overlay
  shared/     zod schemas, serialization, benchmarks asset + loader
scripts/      Offline tooling (benchmark harvester, tag/item-cost generators)
test/         unit / property / integration

Disclaimer

Unofficial tool that reads the local League Client API. It is observation-only and issues no actions, but use it at your own discretion. Counter data comes from third-party sources and is only as current as the cached patch data allows.