BandReady
Offline-first IELTS exam preparation for your own machine.
All four skills, a live AI voice examiner you actually talk to, and band-descriptor scoring — running against AI models you choose, local or cloud. You supply the model; the app is free and there is no account.
Install • Build from source • Contributing • Report an issue
IELTS Prep is an exam room, not a chatbot wrapper. You get authentic question formats, real timers, per-criterion band descriptors and longitudinal progress tracking. It is an Electron app around a Python sidecar that binds to loopback only, so your essays, recordings and scores stay on your disk unless you point it at a cloud provider yourself. > **Not affiliated with the IELTS Partners** (British Council, IDP: IELTS Australia, and > Cambridge University Press & Assessment). IELTS is a registered trademark of its owners. This > is an independent, unofficial project; the name describes the exam it prepares you for and > implies no endorsement by, or association with, the trademark holders. All practice material > here is original and is not official IELTS test content. Band scores produced by this software > are AI-generated estimates for practice only and do not predict official IELTS results. ## Table of contents - [✨ Features](#-features) - [🖼 Screenshots](#-screenshots) - [📥 Install (users)](#-install-users) - [🚀 Build from source](#-build-from-source) - [⚙️ Configuration](#-configuration) - [📚 Documentation](#-documentation) - [🗺 Project layout](#-project-layout) - [👏 Contributing](#-contributing) - [❓ FAQ](#-faq) - [📄 License](#-license) ## ✨ Features ### Speaking - **A live voice examiner** — real-time WebRTC conversation over pipecat 1.5, SmallWebRTCTransport and Silero VAD. You speak, it answers, it follows up. - **Full three-part mocks** — plus single-part drills, topic drills and quick chats when you have ten minutes rather than fifteen. - **A scored report every time** — timed transcript, four criterion bands, quoted evidence for each judgement, and per-turn audio replay. - **496 cue cards** across Parts 1 to 3, in 108 linked card sets over 20 topics. The live call has been confirmed at a real microphone by the project owner. Headless Chromium cannot establish the peer connection, so no automated test guards it. ### Writing - **All three task types** — Academic Task 1 (charts, tables, processes and maps rendered from a structured chart spec), General Training Task 1 letters, and Task 2 essays. - **A pre-check before you spend a model call** — it catches an off-topic or too-short answer while you can still fix it. - **Character-anchored inline annotations** — each error is pinned to the exact offsets in your text, with a fix and an explanation. - **A rewrite loop and an on-demand band 8/9 model answer**, so you can see the gap rather than guess at it. - **102 prompts** with chart specs and band ladders. ### Reading - **Full Academic and General Training tests** — 3 passages, 40 questions, 60 minutes — plus single-passage practice and question-type drills. - **Every IELTS question type**, auto-marked with the real matching rules: case folding, abbreviations, thousands separators, hyphen and space equivalence, word limits. - **A review that explains the trap** — answer key, anchoring paragraph, explanation, and a "why was I wrong?" analysis. - **12 tests** (8 Academic, 4 General Training) over 36 passages, 480 questions, 15 question types. ### Listening - **Audio synthesised on your machine** — Kokoro TTS renders each script per role and per accent on first play, then caches it. A test ships as kilobytes of script, not megabytes of audio. - **Exam-faithful playback** — plays once, no seeking, with a proper transfer window. - **Per-part practice with transcript reveal**, and the same auto-marking engine as Reading. - **7 tests** over 43 scripts across UK, US and AU voices — 415 questions, 8 question types, including map labelling rendered from shipped SVGs. ### Vocabulary, grammar and theory - **An FSRS bank fed by your own mistakes** — errors from your writing and speaking arrive as suggestions you accept or dismiss. - **Six exercise types** — flip, cloze, use-in-sentence with LLM checking, collocation, audio recall and speaking drill. - **A grammar syllabus, not a quiz bank** — 156 points, each naming the false rule learners are usually taught, drilled up a six-rung mastery ladder. - **99 reference articles** across 8 chapters that are never locked, because someone who does not know what a modal is has to be able to look it up first. - **An offline WordNet dictionary** for double-click lookups anywhere in the app. ### Pronunciation - **Fluency signals from your own audio** — speech rate, pause profile, filled pauses, and low-confidence word flagging. - **Minimal-pair perception drills and word-stress tapping**, with reference audio rendered on demand. - **No accent score, ever.** IELTS marks intelligibility, not how close you sound to any particular English. These signals come from speech-recognition confidence, which can tell you a word was hard to make out but cannot tell you a sound was wrong. You get flags and drills, never a pronunciation band and never the word "mispronounced". ### The groundwork - **A placement test that seeds a study plan** — four band estimates become a dated plan with daily sessions that adapts to your results. - **Local SQLite with Alembic migrations**, so an update never means losing your history. - **Jobs with real progress and cancellation** — anything slower than a second returns a job id you poll. - **Importable and exportable `.brpack` content packs**, plus a full data export. - **Dark and light themes**, and keyboard-usable timers and test players. ### Privacy and offline - **The sidecar binds to loopback only** and requires a per-launch bearer token. Nothing else on your machine can call it. - **Provider API keys are encrypted at rest** with a per-install key, masked in the UI, and redacted from every log line. - **Recordings and transcripts stay in your data directory.** There is no BandReady server to send them to. - **Cloud is opt-in and text-only for marking** — if you configure a cloud LLM, it sees the essay or transcript you are having marked, and nothing else. - **Model weights are never bundled and never committed.** They download on first run into your data directory, or get hard-linked from caches you already have. ## 🖼 Screenshots Not captured yet — they land with the first tagged release rather than sitting here as six broken image icons. | Screen | File to drop in | Screen | File to drop in | |---|---|---|---| | **Dashboard** — band trend, streak, today's session | `docs/screenshots/dashboard.png` | **Speaking** — a live session mid-turn | `docs/screenshots/speaking.png` | | **Writing** — the desk with an annotated evaluation | `docs/screenshots/writing.png` | **Reading** — the test player and palette | `docs/screenshots/reading.png` | | **Listening** — a test part with audio playing | `docs/screenshots/listening.png` | **Vocabulary** — the review player | `docs/screenshots/vocab.png` | [`docs/screenshots/README.md`](docs/screenshots/README.md) holds the capture contract. ## 📥 Install (users) Building from source is the supported route today, and it takes about fifteen minutes on a fast connection. Two ways in: 1. **Build it from source.** This is the supported path, and it takes about fifteen minutes on a fast connection. Jump to [Build from source](#-build-from-source). 2. **Run the release workflow yourself.** [`.github/workflows/release.yml`](.github/workflows/release.yml) builds on Windows and macOS runners and attaches installers to a GitHub pre-release. It is `workflow_dispatch` only, so you need push access to this repository or to your own fork. Whichever route you take, the first launch needs a network connection so it can fetch model weights (roughly 340 MB for the voice, more if you want local speech-to-text). Everything after that runs offline. ### Opening it the first time Builds are unsigned, so both systems ask once before they will run a new app. This is what they ask and what to press. - **macOS** — open **System Settings → Privacy & Security**, scroll to the message about the app being blocked, and click **Open Anyway**. Right-click then Open no longer works on macOS 15. - **Windows** — SmartScreen shows "Windows protected your PC". Click **More info**, then **Run anyway**. After that first launch neither system asks again. ## 🚀 Build from source > **Prerequisites** > > | Tool | Version | Why | > |---|---|---| > | [Node.js](https://nodejs.org) | 20 or newer | Electron shell and Vite renderer | > | [pnpm](https://pnpm.io) | 9 (`corepack enable` pins 9.12.0) | workspace package manager | > | [Python](https://python.org) | 3.11 | the sidecar | > | [uv](https://docs.astral.sh/uv/) | latest | Python environment and lockfile | **1. Clone and install the JavaScript workspace.** ```bash git clone https://github.com/Luxshan2000/ielts-prep.git cd bandready pnpm install ``` **2. Install the Python sidecar.** ```bash cd sidecar && uv sync --extra dev --extra voice && cd .. ``` The `voice` extra is large — pipecat, torch and faster-whisper come to roughly 2 to 3 GB of wheels — but it is what makes the live speaking module work. Drop `--extra voice` for a text-only install and everything except live speaking still runs. **3. Start everything.** ```bash node scripts/dev.mjs ``` That spawns the sidecar on a random loopback port, waits for the health handshake, starts Vite, and opens Electron. Useful variants: ```bash node scripts/dev.mjs --browser # no Electron: fixed-port sidecar + Vite in a plain # browser (this is the mode the E2E suite drives) node scripts/dev.mjs --no-mock # use your real configured providers instead of mocks node scripts/dev.mjs --port 5273 --sidecar-port 8710 ``` **4. Run the tests** (optional, but it tells you the install is sound). ```bash uv run --project sidecar pytest sidecar/tests/ -q # 1,352 tests uv run --project sidecar ruff check sidecar/bandready sidecar/tests cd app && pnpm exec tsc --noEmit -p tsconfig.json && pnpm test ``` **5. Package an installer** (optional). The bundle carries a relocatable CPython plus the sidecar's venv, because the app has to run on a machine with no Python. ```bash node scripts/stage-sidecar.mjs --arch arm64 --voice pnpm --filter bandready-app build node scripts/build-electron.mjs cd app && pnpm exec electron-builder --config electron-builder.yml --mac dmg --arm64 ``` Installers land in the repository-root `dist-electron/`. A macOS arm64 DMG comes to roughly 156 MB without the voice extra. `stage-sidecar.mjs` builds the venv for the *host* platform, so a Windows installer cannot be produced from a Mac — that is what the release workflow is for. ## ⚙️ Configuration BandReady never ships a model or an API key. You pick exactly one LLM, one STT and one TTS under **Settings → Providers**. The screen detects what is already running on your machine and offers it first, and **Verify** sends one real request and lists the models the endpoint actually serves. | Preset | Modalities | Kind | Key | Notes | |---|---|---|---|---| | **Ollama** | LLM | local server | none | The easy local start. `ollama pull qwen3:14b`, base URL `http://127.0.0.1:11434/v1`. | | **OpenRouter** | LLM, STT, TTS | cloud | `sk-or-...` | One key covers all three. Base URL is locked to `https://openrouter.ai/api/v1`. | | **Local Whisper** | STT | in-process | none | faster-whisper, int8 on CPU by default. Required for pronunciation feedback. | | **MLX Whisper** | STT | in-process | none | Apple Silicon only. `large-v3-turbo` runs realtime on an M-series chip. | | **Kokoro** | TTS | in-process | none | An 82M ONNX voice model. The default everywhere, and what renders listening audio. | | **Custom OpenAI-compatible** | LLM, STT, TTS | anything | optional | Point it at OpenAI, Groq, DeepSeek, `mlx_lm.server`, `llama.cpp`, or your own gateway. Every field is editable. | Two things are worth knowing. Model fields are closed dropdowns wherever the endpoint can be asked what it serves — a plausible-looking model name that 404s deep inside a provider surfaces three screens away as "the practice engine reported an error", and that is a bad afternoon for someone who came here to practise English. And pronunciation feedback needs *local* Whisper: a remote transcript carries no per-word confidence, so there is nothing for the fluency analysis to read. **Model weights are never bundled.** On first run, onboarding lists what your chosen presets need and downloads it into `/models/` as a resumable, cancellable background job. | Artifact | Used by | Size | |---|---|---| | Kokoro TTS v1.0 (`.onnx` plus voice pack) | Listening audio, the examiner's voice | ~340 MB | | faster-whisper `base` / `small` / `large-v3-turbo` | Local speech-to-text | 145 MB / 484 MB / 1.6 GB | | MLX Whisper large-v3-turbo | Local speech-to-text on Apple Silicon | ~1.5 GB | | English WordNet | Offline dictionary lookups | ~30 MB | If you already have those files, the sidecar finds and **hard-links** them at startup rather than downloading again — it checks `~/.cache/pipecat/kokoro-onnx/`, `~/.cache/huggingface/hub/`, `$HF_HOME`, and anything on `BANDREADY_MODEL_SEARCH_PATH`. Adoption is instant, costs no extra disk, and leaves the original untouched. Set `BANDREADY_ADOPT_LOCAL_MODELS=0` to keep BandReady out of your caches entirely. ### Keeping a key off your disk Set the API key field to the literal `${OPENROUTER_API_KEY}` (any `${VAR}` works) and the key is read from the environment at request time instead of being stored: ```bash export OPENROUTER_API_KEY='sk-or-...' node scripts/dev.mjs ``` Only variables whose names end in `_API_KEY` reach the sidecar. A missing variable produces a precise error naming it, never a silent auth failure. On macOS an app launched from Finder inherits nothing from your shell, so put the value in `/.env` instead — or just paste the key into Settings, where it is encrypted at rest with a per-install key. ### Where your data lives | OS | Path | |---|---| | macOS | `~/Library/Application Support/BandReady` | | Windows | `%APPDATA%\BandReady` | | Linux | `$XDG_DATA_HOME/BandReady` (default `~/.local/share/BandReady`) | One SQLite database, your settings, the encryption key, downloaded models, generated audio, voice recordings and installed content packs all live under that one directory. Deleting it resets the app completely. ## 📚 Documentation - **[CONTRIBUTING.md](CONTRIBUTING.md)** — dev setup, the module map, and how to add a route or a screen. The one document to read before your first change. - **[SECURITY.md](SECURITY.md)** — the security model, what stays local, and how to report a vulnerability privately. - **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)** — Contributor Covenant 2.1. - **[CHANGELOG.md](CHANGELOG.md)** — what has changed, in Keep a Changelog format. - **[docs/IMPLEMENTATION-STATUS.md](docs/IMPLEMENTATION-STATUS.md)** — what is built, what is not, and the evidence for each claim. The most honest file in the repository. - **[docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)** — dev-mode architecture, debugging, log and data locations, resetting local state. - **[docs/REPOSITORY.md](docs/REPOSITORY.md)** — the tree around the code: what is tracked, what is generated, and which docs to trust. - **The content banks**, one document each — what ships, the schema, and how to author more: [reading](docs/READING-CONTENT.md) · [listening](docs/LISTENING-CONTENT.md) · [speaking](docs/SPEAKING-CONTENT.md) · [grammar and vocabulary](docs/GRAMMAR-VOCAB.md) · [theory](docs/THEORY-CONTENT.md). Writing has no such document yet. - **[docs/plan/README.md](docs/plan/README.md)** — the index to twenty-two numbered design documents written before implementation began. This is design *intent*, not status. Much of it shipped differently, and where the plan and the code disagree the code is right. ## 🗺 Project layout ``` bandready/ ├── app/ Electron shell + React renderer (pnpm workspace "bandready-app") │ ├── electron/ main, preload, sidecar spawn/health/teardown — the Node side │ ├── src/features/