Speak Tts
Speak OpenCode answers aloud on Windows. Microsoft Edge neural voices (free, no API key), en-US and tr-TR, with optional LLM summarisation through OpenCode's own model.
1
387
387 in 7 days
38.3
Multi-signal model
8 days ago
2026-10-12
Install and configure
opencode.jsonWrites to this project's opencode.json — applies to this repository only.
opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-speak-tts@0.2.0"]
}Writes to ~/.config/opencode/opencode.json — applies to every project.
~/.config/opencode/opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-speak-tts@0.2.0"]
}If you want to modify the plugin locally, install it into the project and reference the local path.
shell
pnpm add -D opencode-speak-ttsOpenCode loads npm dependencies through its embedded runtime on startup and caches them locally — no manual global install needed.
Speak OpenCode's answers aloud on Windows. Microsoft Edge neural voices, free and with no API key, in any language Edge supports, with optional summarisation for long answers.
- No API key. Synthesis uses Microsoft's free Edge voices. Summarisation runs through a model you have already connected to OpenCode, so there is no second key to configure.
- Reads like a person. Markdown, code fences, file paths, commit hashes and
camelCaseidentifiers are rewritten into speakable prose before synthesis. - Summarises long answers. Past 600 characters the answer is condensed into a few spoken sentences instead of several minutes of audio, without dropping the facts you need to act.
- Four tones for that summary. Default gives the full briefing and drops the assistant's side ideas; Explain keeps them and explains one step simpler; Actions leads with what you have to do; Headlines scans the results as short sentences. Pick one in the summary view.
- Read summaries at your own pace. A summary has its own speed from
0.5xto2x, set in the summary view or changed live from the transport row while it plays. - Never goes silent. Edge's free endpoint breaks without warning. If it does, the plugin drops to Windows' built-in voice and tells you which one it used.
- Controls where you look. Playback, voice, output device and the summary all sit on the row under your message.
Not affiliated with the OpenCode team. This is an independent community plugin.
Voice input
This plugin only speaks responses. For dictation, see opencode-listen-stt — the same V2 plugin family, GPU-accelerated and 99 languages. If you need a Vulkan whisper.cpp build for AMD or Intel, see whisper-vulkan-win.
Requirements
- Windows (playback uses the built-in
MediaPlayerruntime) - OpenCode V2 (
opencode --version→ 2.x) - Python 3.9+ on
PATH pip install edge-tts
Install
opencode plugin add github:fahrim/opencode-speak-tts
pip install edge-tts
That writes the package into plugins in your global opencode.json and
installs it on the next start. Restart OpenCode, then check it loaded:
opencode plugin list # opencode-speak-tts, with no "local" in the source column
Updates are handled by OpenCode itself:
opencode plugin check
opencode plugin update
Prefer a checkout — for development, or if GitHub is unreachable:
git clone https://github.com/fahrim/opencode-speak-tts "$env:USERPROFILE\.config\opencode\plugins\opencode-speak-tts"
A folder in ~/.config/opencode/plugins/ is loaded directly, with no config entry.
Usage
| Command | What it does |
|---|---|
/tts-speak |
Read the latest answer aloud. Press again while it is playing to pause or resume. |
/tts-pause |
Pause playback, or resume it if paused |
/tts-back |
Rewind 15 seconds |
/tts-forward |
Skip ahead 15 seconds |
/tts-stop |
Stop playback |
/tts-toggle |
Speak every answer automatically when the session goes idle |
/tts-voice |
Choose the voice |
/tts-summarize |
Turn summarisation on or off |
/tts-summary |
Open the summary view: the last answer's summary, plus the tone picker |
/tts-summary-language |
Choose the language summaries are written in |
/tts-summary-model |
Choose which model condenses long answers |
/tts-summary-rate |
Choose how fast the spoken summary is read |
/tts-status |
Show what is playing and the current settings |
Every command is also in the palette (ctrl+p) under Speech.
The controls under your message
On the row directly above the composer, at the right, next to OpenCode's own
[agent] [model] [time] details:
| Control | What it does |
|---|---|
| 🔊 | Speak the last answer. While audio is playing this becomes ⏮ ⏸ ⏭ ■ ↻ — back, pause, forward, stop, replay. All clickable. |
1.5x |
How fast the summary is read. Appears in the transport row while a summary is playing; click to pick another speed. Changing it mid-play carries on from the same point at the new pace. |
→ Speakers (Realtek) |
Where Windows is sending the sound. Read only — click for the full name. |
tr-TR-EmelNeural |
The voice that will read. Click to pick another. |
∑ model-name |
The model used for summarisation. Click to change it. |
☰ summary |
Opens the summary view for the last answer. Works whether or not a summary exists yet — from there you can pick a tone and have one written. Spins while it is being written. |
The output device is deliberately not switchable here. Changing the Windows default endpoint moves every other application on the machine as well, so it stays a question you answer in Windows — this row only tells you where the sound is currently going.
Voices
Edge's catalogue is much larger than any picker can usefully show. This one offers a short list to get you started — English and Turkish — and any other Edge voice name works when you set it by hand:
| Voice | |
|---|---|
en-US-AvaNeural |
Ava — female (default) |
en-US-GuyNeural |
Guy — male |
en-US-JennyNeural |
Jenny — female |
en-US-AndrewNeural |
Andrew — male |
en-US-BrianNeural |
Brian — male |
en-GB-SoniaNeural |
Sonia — British female |
en-GB-RyanNeural |
Ryan — British male |
tr-TR-EmelNeural |
Emel — Turkish female |
tr-TR-DenizNeural |
Deniz — Turkish male |
Any other Edge voice name works too; set it in opencode-speak-tts.json (see below).
Summarisation
An answer longer than 600 characters is condensed before it is read. The result targets three to five sentences, roughly 40–90 words, and that is a target rather than a limit — if keeping it short would cost a fact, the summary runs longer.
Three things condensing deliberately does:
- Keeps what changes your next move. Significant changes, verified results, test outcomes, unresolved bugs, blockers, risks, and anything you have to act on. Length never wins over content.
- Never overclaims. Work that was attempted is not reported as finished, and a result is not called confirmed when the original answer did not confirm it.
- Drops the conversation, not the work. Play-by-play, filler, apologies, questions and closing offers ("let me know if") go. So do code, paths and error codes — those are described in words instead.
If summarisation is off, or the answer is short, the text is read as written.
/tts-summary, or ☰ summary in the footer, opens the summary view. It shows
the summary for the last answer, or says why there is none — no answer, or no
summary written yet — and carries the tone picker described below.
Tones
There are four. The summary view has a Tone line at the top; tap it to open the list and pick one. The choice is saved and also applies the next time a long answer is read aloud.
| Tone | What it does |
|---|---|
| Default | The full briefing: the outcome, why it matters, and anything still unresolved. It drops the assistant's speculative plans and unsent offers, and never reports attempted work as finished. |
| Explain | One step simpler than the answer itself. It keeps any idea the assistant proposes, even in passing, and explains why something matters rather than condensing it. |
| Actions | For when you are about to act: it leads with what you have to do, in order, and says plainly when nothing is needed of you. Only a real requirement becomes a task — a passing suggestion does not. |
| Headlines | For scanning by ear: the results as a run of short sentences, three to five words each, with no explanation in between. It trades completeness for speed on purpose, but a blocker or failure is still its own headline. |
These are not four lengths of the same thing. Two habits are common to all of
them — keep what changes your next move, and drop the conversation rather than
the work — and the tones differ over everything else: what to lead with, how much
to explain, and whether to keep the assistant's side ideas. Default and
Explain disagree on the last of those; Actions and Headlines point
the whole summary at a different question. Set the one you use by default with
summaryStyle in the config file.
Speed
A summary has its own reading speed, separate from the voice's. The summary view carries a Speed line next to Tone, and while a summary is playing the speed appears in the transport row beside the playback buttons; either opens the same list:
| Speed | |
|---|---|
0.5x 0.75x |
Slower than the voice |
1x |
Normal (the default) |
1.25x 1.5x 2x |
Faster |
It is absolute, not a nudge on top of the voice's rate: 1x is the voice's
normal pace whatever the voice is set to. Changing the speed while a summary is
playing takes effect at once, and keeps its place — the clip already on the
speakers was generated at the old pace and cannot change, so the sentence in
progress is re-made at the new speed and the playhead is moved to the same point
in the words. You hear it carry on faster, not start over. It applies to
summaries only; a short answer read as written still uses the voice's rate.
Language
Summaries follow the language of the answer unless you choose one with
/tts-summary-language — Turkish in, Turkish out, or any other language you
prefer. Nothing is inferred from the characters in the text, so an answer in a
script this code has never seen still comes back in its own language.
Which model
By default the session's own model does the condensing, in a short-lived child
session that is deleted afterwards. It never touches your conversation history.
Pick a different one with /tts-summary-model if you would rather not spend the
session model on this.
Configuration
Settings persist automatically when you change them from the commands above. To
set defaults by hand, create ~/.config/opencode/opencode-speak-tts.json:
{
"voice": "tr-TR-EmelNeural",
"engine": "edge",
"rate": "+10%",
"pitch": "+0Hz",
"volume": 100,
"summarize": true,
"summaryLanguage": "Turkish",
"summaryStyle": "default",
"summaryRate": "1",
"summaryModel": "",
"autoSpeak": false
}
| Key | Default | Meaning |
|---|---|---|
voice |
en-US-AvaNeural |
Edge voice name |
engine |
edge |
edge (neural, online) or sapi (offline) |
rate |
+0% |
Speaking rate, e.g. +10% |
pitch |
+0Hz |
Pitch offset |
volume |
100 |
Playback volume, 0–100 |
summarize |
true |
Condense long answers before reading |
summaryLanguage |
"" |
Language of the summary; empty means the answer's own |
summaryStyle |
"default" |
Tone of the summary: "default", "explain", "actions" or "headlines" |
summaryRate |
"1" |
Summary reading speed: "0.5", "0.75", "1", "1.25", "1.5" or "2" |
summaryModel |
"" |
Model that condenses; empty means the session's own |
summaryVariant |
"" |
Reasoning-effort variant of that model; empty means its default |
autoSpeak |
false |
Speak automatically when a session goes idle |
Unknown keys and wrongly-typed values are ignored rather than fatal, so a typo cannot stop the plugin loading.
Precedence runs what you picked in the TUI → the config file → the built-in
default. Picking a voice from the dialog overrides the file, and the file
overrides the default — so a voice or summaryLanguage you set by hand is not
undone by restarting OpenCode.
Engines
| Engine | Quality | Needs network | Notes |
|---|---|---|---|
edge |
Natural, neural | Yes, at synthesis time | Default. Microsoft Edge voices, free. |
sapi |
Robotic | No | Windows built-in speech. Works fully offline. |
With engine: "edge" the plugin tries Edge first and falls back to SAPI if
Edge is missing, unreachable or failing. The first time that happens you get a
warning toast saying so — a voice that suddenly sounds like a 1990s navigator is
alarming if nobody explains why it changed — and the fallback is logged.
Choosing sapi explicitly means SAPI only — no network attempt at all. That is
opting out, not asking for a retry.
The Edge request goes through lib/edge_bridge.py, which tries the address Edge
itself uses now and then whatever your installed edge-tts defaults to. It
exists because Microsoft moved the service and the old address answers 403 while
voice lists keep working — see Troubleshooting.
How it works
- A session goes idle (or you run
/tts-speak). - If the answer is over 600 characters and summarisation is on, the chosen model condenses it into a few sentences in a throwaway child session.
- The text is normalised for speech: code fences collapse to a marker, file paths become file names,
camelCasesplits into words, acronyms are spelled out, and hashes and URLs are dropped. - Each chunk is synthesised and played back through
MediaPlayer, driven by a hidden PowerShell process so playback can be paused, resumed, seeked or stopped.
Troubleshooting
Synthesis fails with a 403, or nothing is said at all. Microsoft's Read Aloud
service moved to api.msedgeservices.com. This plugin ships a bridge that tries
the new address first and falls back to your installed default, but pip install -U edge-tts cannot undo it because the bridge is a plugin file, not an edit to
the package. Confirm the bridge is present and Python is on PATH:
python -c "import edge_tts; print(edge_tts.__version__)"
Test-Path "$env:USERPROFILE\.config\opencode\plugins\opencode-speak-tts\lib\edge_bridge.py"
The voice is robotic. That is SAPI — Edge failed and fell back, and a
warning toast said so the first time. /tts-status shows the engine you
configured, not the one that answered; the fallback itself is the
engine fell back to sapi line in pipeline.log.
No sound, but the summary is fine. Playback follows whatever Windows calls
the default output — Bluetooth headphones left connected on another device are
the usual cause. The → label in the footer shows where the sound is going.
Summaries are not appearing. The answer was under 600 characters, or
summarize is off. ☰ summary in the footer opens the view regardless, and from
there tapping Tone writes one.
The summary is in the wrong language. /tts-summary-language. Empty means
"same as the answer", not "English".
The summary keeps changing character, or reads as a set of facts with no
point. That is the tone. Default drops the assistant's side ideas; Explain
keeps them. Tap Tone at the top of the summary view to switch.
Looking for a log file that isn't there. Intentional — see below.
Diagnostics
%LOCALAPPDATA%\opencode-speak-tts\pipeline.log is created only when
something fails. A working install never touches the disk and the file does
not exist at all. When it does, it records why: engine fallback, speech failure,
summarisation failure, playback that never started.
Get-Content "$env:LOCALAPPDATA\opencode-speak-tts\pipeline.log" -Tail 20
Development
npm install
npm run typecheck
npm test
License
MIT
Similar plugins
Read Aloud
opencode-read-aloud
OpenCode TUI plugin: read the last answer aloud with Speechify or ElevenLabs, highlighting each word as it is spoken.
Smart Voice Notify
opencode-smart-voice-notify
Smart voice notification plugin for OpenCode with multiple TTS engines (ElevenLabs, Edge TTS, Windows SAPI), AI-generated dynamic messages, and intelligent reminder system
Hub
@huynle/hub
Hub: a harness-agnostic central hub your AI agent harnesses connect to (OpenCode first). Follow, steer and stop sessions from your phone or browser, with notifications, optional hands-free Voice and a self-hostable server.