Getting started
Run one command for your OS (needs git; on Windows, Git for Windows). It installs uv, Python, ONNX models, Cursor / Claude / Codex hooks, the daemon, and enables spoken TTS under ~/aftertone or %USERPROFILE%\aftertone. In Cursor you only turn on Hooks and trust the workspace.
Watch the demo on the homepage hero — short dev question and a longer clone-repo task with Claude Code (/aftertone_on).
- Nothing to pay for Aftertone speech — open source, no subscription
- No API keys — localhost daemon only; no cloud TTS signup
- No Hugging Face token required for default model download (public weights)
- Your coding agent may still need its own plan — separate from Aftertone
curl -fsSL https://raw.githubusercontent.com/omarelkhal/aftertone/main/scripts/install.sh | bash -s -- --install-uv --start-daemon
irm https://raw.githubusercontent.com/omarelkhal/aftertone/main/scripts/install.ps1 | iex
Free to use — nothing to pay for speech
Aftertone is the local TTS layer only. It does not replace your coding agent and does not require its own paid plan.
| Cost | $0 — open source (MIT). No paid tier for speech. |
|---|---|
| API keys | None — tts_daemon on localhost; no cloud TTS account. |
| Subscription | None for Aftertone — install once under ~/aftertone or %USERPROFILE%\aftertone. |
| Models | Public Hugging Face weights — no HF token required for the default download. |
| Languages | /aftertone-lang — write <spoken_summary> in that language (no auto-translation). |
Your coding agent (Cursor, Claude, etc.) may still need its own subscription — separate from Aftertone.
Then in Cursor
- Enable Hooks in Cursor Settings and trust each workspace where you want TTS.
- Optional: run
/aftertone-statusin Agent chat. - Ask the agent something — listen for one short line after the reply.
Then in Claude Code
- Run
claude(no--plugin-dirrequired after global install). - In chat:
/aftertone_onto enable speech for the session. - Substantive replies need a
<spoken_summary>tag at the end — see Claude slash commands.
Then in Codex
- Run
codexnormally. - Review or trust the installed
~/.codex/hooks.jsonentry if Codex prompts for hook trust. - Enable this session with
$aftertone-on, or open/skillsand chooseaftertone-on. - The skill sends one reply with a
<spoken_summary>tag so the Stop hook registers the session id.
Optional: manual clone, other projects, smoke test
Manual: git clone … && bash scripts/bootstrap.sh and
uv run --directory py python -m aftertone restart.
Other repo: bash -s -- --into ..
Verify: bash py/test_speak_summary_pipeline.sh.
scripts/README.md.
Slash commands
After a global install, use slash commands in Cursor or Claude Code. In Agent chat, type / and pick an aftertone- (Cursor) or aftertone_ (Claude) command. Each runs one python -m aftertone … from your install dir — do not hand-edit speak_summary.toml for everyday changes.
Cursor (hyphens)
Commands live in ~/.cursor/commands/ and work in any trusted workspace.
| Command | What it changes | Daemon restart? |
|---|---|---|
/aftertone-toggle | Flip spoken TTS on/off | No |
/aftertone-on · /aftertone-off | Force on or off | No |
/aftertone-status | Show settings + daemon health | No |
/aftertone-lang | Language (syncs agent spoken-summary rule) | No |
/aftertone-speed | Playback speed | No |
/aftertone-mode | queue or interrupt | No |
/aftertone-expression | Inline Supertonic expression tags (off default; optional subtle / expressive) | No |
/aftertone-voice | Voice picker (human names, e.g. Sara (female), James (male)) | Yes (command restarts for you) |
/aftertone-restart | Reload daemon after port, voice_*, onnx_dir, or use_gpu change | Yes |
/aftertone-doctor | Diagnostics (hooks, config, daemon, assets) | No |
/aftertone-repair | Re-register global hooks, apply install defaults, restart daemon | Yes |
F1–F5, M1–M5). You can pass a first name to the config CLI (e.g. Sara → F4).
Command definitions: .cursor/commands/ on GitHub.
Claude Code (underscores)
Global install registers Stop hooks in ~/.claude/settings.json and copies slash commands to ~/.claude/commands/. After install: run claude, then /aftertone_on to enable speech for the session.
| Command | What it does | Instant? |
|---|---|---|
/aftertone_on · /aftertone_off | Enable or disable spoken TTS | Yes |
/aftertone_toggle · /aftertone_status | Flip enabled · JSON status | Yes |
/aftertone_restart · /aftertone_doctor · /aftertone_repair | Daemon · diagnostics · repair | Yes |
/aftertone_lang · /aftertone_speed · /aftertone_mode | Language · speed · queue mode | Picker |
/aftertone_voice · /aftertone_expression | Voice (restarts daemon) · expression tags | Picker |
Guide on GitHub: docs/adapters/claude.md · command sources claude-plugin/aftertone/commands/
Codex (skills)
Global install registers a Codex Stop hook in ~/.codex/hooks.json, guidance in ~/.codex/AGENTS.md, and user skills in ~/.agents/skills/aftertone-*. Invoke them with $aftertone-on or through /skills. CLI commands remain available as terminal fallbacks.
| Command | What it does |
|---|---|
$aftertone-on · $aftertone-off | Enable or disable spoken TTS for the current Codex session |
$aftertone-status | Show config, daemon, and enabled sessions |
python -m aftertone session list | Confirm the Codex session id was registered |
Guide on GitHub: docs/adapters/codex.md
Aftertone CLI (v2)
From your install root (path in ~/.cursor/hooks/aftertone-install-dir or %USERPROFILE%\.cursor\hooks\aftertone-install-dir):
cd "$(cat ~/.cursor/hooks/aftertone-install-dir)"
uv run --directory py python -m aftertone status
uv run --directory py python -m aftertone on
uv run --directory py python -m aftertone set lang fr
uv run --directory py python -m aftertone set voice F4 --ensure
uv run --directory py python -m aftertone restart
uv run --directory py python -m aftertone doctor
uv run --directory py python -m aftertone repair
set voice restarts the daemon by default. set lang syncs spoken-summary.mdc. Install runs apply-defaults (summary_mode = tag_only, total_step = 8, spoken_summary_max_sentences = 0 for the full tag).
Cursor setup
Hooks enabled
Cursor Settings → Hooks should show no errors for this project’s .cursor/hooks.json.
Trusted workspace
Project hooks do not run in restricted mode. Trust the folder when Cursor asks.
version: 1
.cursor/hooks.json must include "version": 1 (numeric) or hooks fail to load.
What runs: afterAgentResponse → speak_summary.sh → speak_summary_prepare.py → POST /say on the daemon. The hook uses inline reply text from the payload (not the transcript file).
uv run --directory py python -m aftertone doctor, bash py/diagnose_speak_hooks.sh, or inspect .cursor/hooks/state/hook_payload_trace.jsonl for afterAgentResponse with inline_after_response_ok: true.
Claude setup
Claude Code uses global Stop hooks in ~/.claude/settings.json, slash commands in ~/.claude/commands/, and the shared local daemon. After global install, run claude, then enable speech for that chat with /aftertone_on.
Per-chat activation
/aftertone_on enables this Claude chat only. Use /aftertone_off to disable the current chat without changing other sessions.
Global hooks
Install registers Stop and SubagentStop hooks once. The hooks stay silent until the session is enabled.
Spoken rule
~/.claude/rules/spoken-summary.md tells Claude to end substantive replies with one <spoken_summary> block.
/aftertone_status inside Claude Code, or follow the full Claude adapter guide.
Codex setup
Codex uses the official Codex hooks Stop event. Aftertone reads last_assistant_message from the hook JSON and passes it through the shared local TTS pipeline.
Stop payload smoke test in docs/adapters/codex.md, then test one real Codex reply with a <spoken_summary> tag.
Daemon commands
Prefer /aftertone-restart or uv run --directory py python -m aftertone restart after changing voice, port, ONNX path, or GPU. /aftertone-voice restarts for you.
Recommended: from install root — uv run --directory py python -m aftertone {status|restart} (see Aftertone CLI).
Lower level — run from py/ with --repo-root ..:
cd py
uv run python tts_daemon_ctl.py start --repo-root ..
uv run python tts_daemon_ctl.py status --repo-root ..
uv run python tts_daemon_ctl.py restart --repo-root ..
uv run python tts_daemon_ctl.py stop --repo-root ..
| Change in TOML | Need restart? |
|---|---|
speed, lang, total_step, mode, expression_mode, quiet_hours, text limits, heuristics | No — read each hook run |
port, onnx_dir, voice_*, use_gpu | Yes — restart |
.cursor/hooks/state/tts-daemon.port. If you change port in TOML without restarting, speech may go to the wrong port. Run restart after changing port.
Configuration
Use slash commands for enabled, lang, speed, mode, and voice_type. The file .cursor/hooks/speak_summary.toml is updated by those commands; edit it directly only for advanced keys. Common knobs:
| Key | What it does |
|---|---|
enabled | Master switch — false silences all speech |
lang | ONNX language and the language agents should use in <spoken_summary> (no auto-translation) |
voice_type / voice_style | Preset id (e.g. F4) or path to voice JSON; pick via /aftertone-voice |
speed | Playback speed (1.0 = baseline) |
quiet_hours | e.g. 22:00-08:00 — no speech in that window (local clock) |
spoken_summary_max_chars | Cap for tag text (default 360) |
summary_mode | tag_only (repo default) = only <spoken_summary>; auto = tag or heuristic excerpt; heuristic = excerpt only |
only_speak_spoken_summary | Legacy alias for tag_only when summary_mode is unset |
total_step | ONNX denoising steps (default 8 — quality vs CPU) |
expression_mode | off default; optional inline tags via /aftertone-expression |
/aftertone-lang updates lang and syncs the Cursor rule automatically. Manual sync from repo root: uv run --directory py python sync_spoken_rule_lang.py
Spoken summaries
Best quality is a flow briefing for vibe coding: what happened, why it matters, optional next move — not a changelog. Put one tag at the very end of substantive replies:
<spoken_summary>
Tests pass and the daemon is restarted!! Your next reply should sound cleaner??
</spoken_summary>
The hook uses the last </spoken_summary> and pairs it with the nearest opening tag before it. Do not leave a bare <spoken_summary> in code or prose above your real tag (that can swallow the wrong text).
Repo default summary_mode = "tag_only" — no speech when the tag is missing (set summary_mode = "auto" in TOML for heuristic fallback). For livelier delivery, end each sentence in the tag with !!, ??, ?!, or !?. Inline <sigh> tags are optional via /aftertone-expression (default off).
Rules for agents: .cursor/rules/spoken-summary.mdc.
Windows hook latency (Cursor, not Aftertone)
If speech feels slow on Windows but the install looks healthy, split the wait into two parts: (1) Cursor calling the hook after the reply, and (2) Aftertone preparing audio once the hook runs. On Linux and macOS, part (1) is usually much smaller; on Windows it often dominates.
What Aftertone does once the hook runs: read the reply text, POST to the local tts_daemon, synthesize, and play. That path is typically a few seconds on CPU, tunable with total_step and voice settings.
What Aftertone cannot fix: how long Cursor waits after the assistant message before it invokes afterAgentResponse.
How to verify on your machine (install dir, e.g. %USERPROFILE%\aftertone):
.cursor/hooks/state/speak_summary-hook.log— step lines taggedlatency.cursor/hooks/state/pipeline_trace.jsonl— full trace per reply (phase: hookandphase: sound)hook_minus_transcript_mtime_ms— proxy for Cursor delay after the transcript file was last writtenfirst_sound_since_hook_ms— Aftertone time from hook start to first audio
Run /aftertone-doctor or uv run --directory py python -m aftertone doctor after a slow reply. Updating Cursor may help; comparing the same project on Ubuntu or macOS is the clearest A/B test.
Troubleshooting
Nothing speaks
Run /aftertone-doctor or uv run --directory py python -m aftertone doctor, then bash py/test_speak_summary_pipeline.sh. Check /aftertone-status, Cursor Hooks panel, trusted workspace, and speak_summary-hook.log.
Wrong language or accent
Match lang in TOML and write <spoken_summary> in that language. Run sync_spoken_rule_lang.py after changing lang.
TOML change had no effect
Voice, port, GPU, and ONNX path need /aftertone-restart or tts_daemon_ctl.py restart. Speed, lang, and expression_mode apply on the next hook without restart.
Speech feels very delayed (Windows)
Often mostly Cursor hook scheduling, not Aftertone synthesis. See Windows hook latency and the Cursor forum thread. Check pipeline_trace.jsonl for hook_minus_transcript_mtime_ms vs first_sound_since_hook_ms.
Wrong text spoken
Usually an unclosed <spoken_summary> mention earlier in the reply, or no tag at the end with summary_mode = tag_only. Put only one closing block at the bottom, or set summary_mode = "auto".
Quiet hours
Clear quiet_hours or test with SPEAK_SUMMARY_IGNORE_QUIET=1 in the environment.
Full reference
Every TOML key, language list, and log file paths:
- .cursor/hooks/README.md — hook + daemon reference
- AGENTS.md — short agent-oriented digest
- py/README.md — Python env
- py/aftertone/README.md — v2 package (
python -m aftertone) - README.md — project overview