Alan2Z/dsh-speak
一种能让你的harness开口说话的方案/Make your AI harness speak — a verified voice-announcement solution
Listed
2
Voice
Bundle verified
Preview
What it does
Voice-announce the final reply on Windows (SAPI5 natural voices) and macOS (system voice); skips reasoning and tool calls, one-line npm install.
Best for
- Windows or macOS users who want an audible notification when an agent finishes a long task.
- Users who need approval requests and agent questions announced without hearing reasoning or tool-call narration.
- Harness integrations that can pass final reply text to the provided SAPI5 or macOS speech engine.
Not ideal for
- Linux environments or systems without Windows SAPI5 or the macOS `say` command.
- Users who need reasoning, tool activity, or the full event stream narrated.
- Workflows expecting active long-term maintenance; the project states that further updates are unlikely except for unexpected circumstances.
README
dsh-speak 🔊 — Voice announcements for AI coding harnesses
English · 中文

Let your agent tell you when a long task is done — no more staring at the screen.
dsh-speak reads the final assistant reply aloud through system speech synthesis —
on Windows using natural voices (Windows 11 built-in, or
NaturalVoiceSAPIAdapter on Windows 10) with graceful fallback to stock voices;
on macOS using the built-in say (can follow a Siri natural voice). It was built
for DeepSeek Harness
and is structured so any harness can plug in.
Project status: this project exists only to provide an already-verified solution for users who want their harness to speak. Barring unexpected circumstances, it will not be updated further.
TL;DR — install for DSH
-
Install the package into your web profile (pick one):
dsh plugin --profile web add dsh-speak # or, without pnpm: npm install --prefix "$env:USERPROFILE\.dsh\profiles\web" dsh-speakOn macOS (bash):
npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak -
Append to
~/.dsh/profiles/web/cordis.patch.yml:- insert: - id: speech-hook name: 'dsh-speak' -
Restart the DSH web app — replies are now announced aloud.
Let your agent do it? Paste this repo URL (
https://github.com/Alan2Z/dsh-speak) into your DSH session and ask it to install the plugin — your agent follows this very README. Approving the out-of-workspace writes (~/.dsh) is all that’s needed.
harness event (DSH session event / Claude Code Stop hook / anything)
│
▼ adapters/… (harness-specific trigger: filter, throttle, cancel)
▼ engine/speak.ps1 / speak.sh (harness-agnostic: clean text → SAPI5 / say)
▼ 🔊 you hear the final reply
Features
- Automatic: DSH web plugin watches the session event stream and announces the final reply (skips reasoning/tool-call narration, merges multi-step messages).
-
Gets your attention: announces approval requests (hears “需要你的审批” when
the agent is waiting on you) and questions the agent asks via
ask_user_question. -
Bundle auto-registration (1.3.0): declare the package in
dsh.profile.bundlesand the plugin registers itself via the bundledcordis.patch.yml— no manual patch entry needed. - Best-effort: never throws, never blocks the harness, never breaks a session.
- Natural voices: Windows prefers natural voices — Windows 11 built-in packs, or voices registered via NaturalVoiceSAPIAdapter on Windows 10 (e.g. Xiaoxiao); macOS uses the system reading voice (Siri natural voices on recent macOS). Both fall back to any installed voice.
- Robust text cleaning: strips markdown/URLs/emoji that make speech synthesis fail silently, and guards the adapter’s per-utterance character ceiling.
-
Portable engine: any process can speak with one line:
Windows
powershell -File speak.ps1 -Text "你好"/ macOS./speak.sh -t "你好".
Prerequisites
Windows:
- Windows 10 or 11, PowerShell (any recent version).
- Natural voices:
- Windows 11: natural voice packs are built into the system — no extra installation. Enable/switch them in Settings → Accessibility → Narrator or Settings → Time & Language → Speech.
- Windows 10: install NaturalVoiceSAPIAdapter and use its VoiceDownloader to download the natural voice pack(s) you want (Chinese or any other language).
- Without natural voices, the engine falls back to a stock voice (e.g. Huihui).
macOS:
- macOS (Apple Silicon or Intel), built-in
saycommand — no extra software. - Chinese voices: see the macOS section (incl. the Siri natural-voice picker and its pitfalls).
Quick start — DSH
Option A — npm plugin (recommended)
# 1. install the plugin into your web profile (adds dsh-speak to
# ~/.dsh/profiles/web/package.json dependencies)
dsh plugin --profile web add dsh-speak
# 2. register it in ~/.dsh/profiles/web/cordis.patch.yml
# (for npm packages the bare package name is used — no file:/// URL needed):
# - insert:
# - id: speech-hook
# name: 'dsh-speak'
# 3. restart the DSH web app — replies are now announced automatically
No pnpm?
dsh pluginforwards to pnpm, which is not installed on every machine. The exact same install can be done with npm directly:npm install --prefix "$env:USERPROFILE\.dsh\profiles\web" dsh-speak
The engine ships inside the package (node_modules/dsh-speak/engine/), so no extra
copying is needed.
Option B — file install (no npm needed)
# 1. clone
git clone https://github.com/Alan2Z/dsh-speak.git
cd dsh-speak
# 2. one-command install: copies engine + plugin, registers in cordis.patch.yml
powershell.exe -NoProfile -ExecutionPolicy Bypass -File adapters\dsh\install.ps1
# 3. verify the engine speaks
powershell -NoProfile -ExecutionPolicy Bypass -File "$env:USERPROFILE\.dsh\hooks\speak.ps1" -Text "你好,语音播报已就绪。"
# 4. restart the DSH web app — replies are now announced automatically
What the file installer did:
| file | destination |
|---|---|
engine/*.ps1 |
%USERPROFILE%\.dsh\hooks\ |
adapters/dsh/speech-hook.js |
%USERPROFILE%\.dsh\profiles\web\plugins\ |
| registration entry | appended to %USERPROFILE%\.dsh\profiles\web\cordis.patch.yml (backed up first) |
macOS
The same adapter runs natively on macOS — the plugin auto-detects the platform and
calls engine/speak.sh (the built-in say command) instead of speak.ps1.
Since 1.2.0 the macOS engine ships in the npm package — no extra software.
Install (npm — same as Windows)
# 1. install into your web profile (no pnpm needed — only `dsh plugin` requires it)
npm install --prefix "$HOME/.dsh/profiles/web" dsh-speak
# 2. register in ~/.dsh/profiles/web/cordis.patch.yml (bare package name — no file:/// URL):
# - insert:
# - id: speech-hook
# name: 'dsh-speak'
# 3. no restart needed — the patch watcher hot-reloads; pure-text replies are
# announced after ~1.5 s (tool-calling replies are intentionally not announced)
With pnpm installed,
dsh plugin --profile web add dsh-speakworks identically.
Voices (important — two pitfalls)
- By default the engine follows the system reading voice (Settings → Accessibility → Spoken Content → System Voice). On macOS 26 that picker has an ⓘ circle icon next to it — click it for the full voice list; the plain dropdown does not contain the Siri natural voices. Pick e.g. “普通话 Siri 声音1(男声)” there.
-
Siri voice (Settings → Siri → Voice) and the system reading voice are
two independent settings; Siri voices are not exposed to
say -v '?'and cannot be selected by name — they only work as the system default. - ⚠️ Pitfall 1 (reproduced): opening the “Spoken Content / Siri Voice” settings pane — even without changing anything — drifts/resets the system voice to the classic “婷婷 (Tingting)”. If the voice suddenly changes, re-pick it via the ⓘ entry.
- ⚠️ Pitfall 2: the log lives at
$TMPDIR/dsh-speech-hook.log(os.tmpdir()— not/tmp). - Use
-v Eddy|Flo|Tingtingto force a specific voice (say -v '?'lists them). -
sayhas no volume flag — volume follows the system output volume.
Test the engine alone (no DSH needed)
curl -sfL -o ~/speak.sh "https://cdn.jsdelivr.net/gh/Alan2Z/dsh-speak@main/engine/speak.sh"
chmod +x ~/speak.sh
~/speak.sh -t "你好,Mac 版语音播报测试"
~/speak.sh -t "测试" -v Eddy -r 200 # explicit voice + rate
Quick start — Claude Code
Register the Stop hook in ~/.claude/settings.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\\path\\to\\dsh-speak\\adapters\\claude-code\\stop-hook.ps1"
}
]
}
]
}
}
Quick start — any other harness
Call the engine directly from your agent / wrapper / script:
# announce a one-liner
powershell -NoProfile -ExecutionPolicy Bypass -File engine\speak.ps1 -Text "构建完成"
# announce a long summary (from a file)
powershell -NoProfile -ExecutionPolicy Bypass -File engine\speech-summary.ps1 -Text "…"
# ask for user attention (blocking, for prompts/approvals)
powershell -NoProfile -ExecutionPolicy Bypass -File engine\speech-prompt.ps1 -Text "请做出选择"
Configuration
Engine parameters (see docs/DESIGN.md):
speak.ps1 -Text "…" -Volume 50 -Rate 1 -MaxChars 300 -LongTextMessage "本次播报内容较长,请自行阅读。"
DSH plugin configuration — prefer the profile patch config block (visible in
dsh --dump-config, per-profile, survives npm updates):
# ~/.dsh/profiles/web/cordis.patch.yml
- insert:
- id: speech-hook
name: 'dsh-speak'
config:
throttleMs: 1500 # merge delay before announcing (ms)
engine: '' # engine path override; '' = auto-resolve
announceApprovals: true # speak approval requests
announceQuestions: true # speak ask_user_question content
stripApprovalPrefix: true # strip the "escalate sandbox to ...: " prefix
longTextMode: message # message | heading (speak largest md heading)
maxChars: 300 # engine per-utterance ceiling
volume: 50 # Windows only
rate: 0 # 0 = engine default (Windows SAPI scale / macOS wpm)
See docs/CUSTOMIZATION.md for the full customization guide.
Customizing (survives npm updates)
You can tune behavior without forking, and your changes survive npm update:
-
Copy the engine out and edit it (recommended — this is where defaults live: volume, rate,
MaxChars,LongTextMessage, voice logic):# Windows Copy-Item "$env:USERPROFILE\.dsh\profiles\web\node_modules\dsh-speak\engine\speak.ps1" "$env:USERPROFILE\.dsh\hooks\my-speak.ps1" # macOS cp ~/.dsh/profiles/web/node_modules/dsh-speak/engine/speak.sh ~/.dsh/hooks/my-speak.shThen point the plugin at your copy in the
configblock:- insert: - id: speech-hook name: 'dsh-speak' config: engine: 'C:/Users/<you>/.dsh/hooks/my-speak.ps1' # or ~/.dsh/hooks/my-speak.sh on macOSThe plugin resolves the engine as
config.engine→ package engine →~/.dsh/hooks/, so your copy wins.npm updateonly touches the package — your engine stays. -
Edit the file inside
node_modules— works, but the nextnpm updateoverwrites it. -
Fork the repo — full control, publish your own package if you want.
Troubleshooting
| symptom | cause | fix |
|---|---|---|
| No sound at all, no error | no natural voice enabled/installed | Win11: enable a natural voice in Settings → Narrator / Speech; Win10: install NaturalVoiceSAPIAdapter + a voice pack. Test speak.ps1 directly |
| Long replies never spoken | adapter per-Speak character ceiling |
already guarded at 300 chars — lower -MaxChars if needed |
| Emoji-heavy text silent | SAPI fails silently on emoji | already stripped by the engine |
| Plugin not loading | raw Windows path as plugin name | use the file:///C:/… URL form (installer does this) |
| macOS: voice suddenly became “婷婷” | opening the “Spoken Content / Siri Voice” pane drifted the system voice | re-pick via Settings → Accessibility → Spoken Content → System Voice → ⓘ entry |
macOS: no log at /tmp
|
os.tmpdir() is /var/folders/.../T, not /tmp
|
log is at $TMPDIR/dsh-speech-hook.log
|
Plugin diagnostics: Windows %TEMP%\dsh-speech-hook.log; macOS $TMPDIR/dsh-speech-hook.log
Repository layout
engine/ harness-agnostic speech engine (PowerShell + SAPI5 / bash + say)
speak.ps1 / speak.sh clean + speak (the only seam any adapter needs)
speech-prompt.ps1 blocking short announcement
speech-summary.ps1 blocking reply-summary announcement
adapters/
dsh/ DSH web plugin + one-command installer
speech-hook.js session-event trigger (throttle + tool-call cancel)
install.ps1 copies + registers + backs up
claude-code/
stop-hook.ps1 Claude Code Stop hook trigger
docs/
DESIGN.md full design rationale, pitfalls, extension guide
Writing a new adapter
Three reference patterns exist: event-stream (DSH), stop-hook (Claude Code),
agent-called (speech-summary.ps1 from a shell). In every case the adapter only
needs to: capture the final reply text → invoke the engine. See
docs/DESIGN.md §7.
License
MIT — see LICENSE.
Frequently Asked QuestionsFAQ
Use the verified command dsh plugin --profile default add github:Alan2Z/dsh-speak in a DSH-enabled shell. The command resolves the public package metadata and keeps the plugin attached to the catalog identity shown on this page.
Compatibility follows the bundle and profile status shown above. If a profile is not detected, keep the plugin disabled there and check the repository documentation before enabling it in production.
The GitHub link and activity metadata are the source of truth for releases and maintenance. Revisit this page after a new release to confirm the catalog has observed the latest version.