Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ curl -fsSL https://raw.githubusercontent.com/eladcandroid/claude-code-voice/main

## Supported languages

en, he, es, fr, de, ja, ko, pt, it, ru, zh, ar, hi, tr, nl, pl, uk, el, cs, da, sv, no — and any other language Apple's SFSpeechRecognizer supports.
en, he, es, fr, de, ja, ko, pt, it, ru, zh, zh-hk, ar, hi, tr, nl, pl, uk, el, cs, da, sv, no — and any other language Apple's SFSpeechRecognizer supports.
Comment on lines 21 to +23

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLAUDE.md still shows install/uninstall commands using eladcandroid earlier in the file, while this PR updates other docs/scripts to dr-data and/or dynamic URL display. This inconsistency will confuse users following CLAUDE.md; align the commands with the new default username (or update the doc to instruct users to use their fork).

Copilot uses AI. Check for mistakes.
44 changes: 33 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

# Claude Code Voice (macOS)

Adds native on-device speech-to-text to Claude Code's `/voice` command using Apple's `SFSpeechRecognizer`. Natively supported languages proxy to Anthropic's server; unsupported languages (Hebrew, Arabic, etc.) transcribe locally via Apple STT. No API keys, no binary patching — survives Claude Code updates.
Adds native speech-to-text to Claude Code's `/voice` command using Apple's `SFSpeechRecognizer`. Claude-native languages are proxied to Anthropic's voice server, while unsupported languages (Hebrew, Arabic, etc.) are transcribed locally with Apple STT. No API keys, no binary patching — survives Claude Code updates.

## Quick install

```bash
curl -fsSL https://raw.githubusercontent.com/eladcandroid/claude-code-voice/main/setup.sh | bash
curl -fsSL https://raw.githubusercontent.com/dr-data/claude-code-voice/main/setup.sh | bash
```

## Requirements
Expand Down Expand Up @@ -65,32 +65,54 @@ Type `/config` in Claude Code to change the language. The voice server picks it
| Norwegian | `no` | Anthropic |
| **Hebrew** | `he` | Apple STT |
| **Arabic** | `ar` | Apple STT |
| **Chinese** | `zh` | Apple STT |
| **Chinese (Mandarin)** | `zh` | Apple STT |
| **Cantonese (Hong Kong)** | `yue` or `zh-hk` | Apple STT |

Any language supported by Apple's `SFSpeechRecognizer` works — the 20 natively supported languages are proxied to Anthropic's server for best quality.

### Cantonese setup

1. In Claude Code, run `/config` and set the language to `yue` (or `zh-hk`, `cantonese`, `廣東話`, `粵語` — all work).
2. Download the Cantonese speech model on macOS so Apple STT can work offline:
- **System Settings → Keyboard → Dictation**
- Enable **Dictation**, then open the **Language** dropdown
- Click **Add Language…** and select **Chinese (Cantonese)**
- macOS will download the on-device model (~100–200 MB)
3. Once downloaded, restart Claude Code and hold **Space** to speak in Cantonese — transcription runs fully on-device.

## Token usage and local processing

- **Claude-native languages** (shown as `Anthropic` in the table): audio is proxied to Anthropic for transcription, so this path uses Claude's voice backend and may consume tokens/usage.
- **Apple STT languages** (shown as `Apple STT`): transcription is done on your Mac with `SFSpeechRecognizer`, so speech-to-text itself is local.
- In both cases, once text is inserted into Claude Code, your normal chat usage still applies.

## How it works

Claude Code has an undocumented `VOICE_STREAM_BASE_URL` env var that redirects its voice WebSocket. This project runs a native macOS app on `localhost:19876` that acts as a smart router:

- **Native languages** (20) → proxied to Anthropic's voice server with OAuth token from Keychain
- **Other languages** → transcribed locally via Apple's on-device `SFSpeechRecognizer`

```
┌─ native lang ──▶ Anthropic server
┌─────────────┐ audio │ (streaming STT)
│ Claude Code │──chunks──▶│ voice-server
│ /voice + ␣ │◀──text───│
└─────────────┘ └─ other lang ──▶ Apple STT
(on-device)
```text
┌──────────────┐ audio chunks ┌──────────────┐
│ Claude Code │ ────────────────────▶ │ voice-server │
│ /voice + ␣ │ ◀──────────────────── │ (local) │
└──────────────┘ transcript └──────┬───────┘
┌─────────────┴─────────────┐
│ │
native Claude language other language
│ │
▼ ▼
Anthropic voice STT Apple on-device STT
```

Everything is a single Swift binary — WebSocket server, proxy, and speech recognition combined. No external runtimes needed.

## Uninstall

```bash
curl -fsSL https://raw.githubusercontent.com/eladcandroid/claude-code-voice/main/uninstall.sh | bash
curl -fsSL https://raw.githubusercontent.com/dr-data/claude-code-voice/main/uninstall.sh | bash
```

## Project structure
Expand Down
2 changes: 2 additions & 0 deletions scripts/server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ let localeMap: [String: String] = [
"it": "it-IT", "italian": "it-IT", "italiano": "it-IT",
"ru": "ru-RU", "russian": "ru-RU", "русский": "ru-RU",
"zh": "zh-CN", "chinese": "zh-CN",
"yue": "zh-HK", "cantonese": "zh-HK", "廣東話": "zh-HK", "广东话": "zh-HK", "粵語": "zh-HK",
"zh-hk": "zh-HK",
"ar": "ar-SA", "arabic": "ar-SA",
"hi": "hi-IN", "hindi": "hi-IN",
"id": "id-ID", "indonesian": "id-ID",
Expand Down
12 changes: 10 additions & 2 deletions setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ INSTALL_DIR="$HOME/.local/share/claude-code-voice"
if [ ! -f "scripts/server.swift" ]; then
echo "Downloading claude-code-voice..."
rm -rf "$INSTALL_DIR"
git clone --depth 1 https://github.com/eladcandroid/claude-code-voice.git "$INSTALL_DIR" 2>/dev/null
git clone --depth 1 https://github.com/dr-data/claude-code-voice.git "$INSTALL_DIR" 2>/dev/null
cd "$INSTALL_DIR"
fi

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

# Detect GitHub username from this repo's git remote so forked copies display correct URLs
_REMOTE_URL=$(git -C "$SCRIPT_DIR" remote get-url origin 2>/dev/null || echo "")
_GITHUB_USER=$(echo "$_REMOTE_URL" | sed -E 's|.*github\.com[:/]([^/]+)/.*|\1|')
_GITHUB_USER="${_GITHUB_USER:-dr-data}"
Comment on lines +19 to +22

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub username extraction falls back incorrectly when origin is not a GitHub URL: the sed substitution returns the original URL unchanged (non-empty), so _GITHUB_USER becomes something like https://gitlab... and the raw.githubusercontent.com links printed later will be invalid. Consider extracting with a pattern that outputs only on match (e.g., sed -nE ...p) and/or explicitly validating the result before using the default.

Copilot uses AI. Check for mistakes.
SCRIPTS="$SCRIPT_DIR/scripts"
APP="$SCRIPTS/VoiceServer.app"

Expand Down Expand Up @@ -116,5 +121,8 @@ echo ""
echo "=== Done ==="
echo "Restart Claude Code, enable /voice, and speak."
echo "Switch language with /config."
echo "Supported /config language codes:"
echo " en, es, fr, de, ja, ko, pt, it, ru, hi, id, pl, tr, nl, uk, el, cs, da, sv, no, he, ar, zh, zh-hk"
echo " plus any language supported by Apple SFSpeechRecognizer"
Comment on lines +124 to +126

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The printed list of “Supported /config language codes” omits yue, even though the PR adds Cantonese support and README suggests using yue. This can mislead users into thinking yue isn’t accepted; update the displayed list (or wording) to include yue (and any other newly-supported aliases you consider canonical).

Copilot uses AI. Check for mistakes.
echo ""
echo "Uninstall: curl -fsSL https://raw.githubusercontent.com/eladcandroid/claude-code-voice/main/uninstall.sh | bash"
echo "Uninstall: curl -fsSL https://raw.githubusercontent.com/$_GITHUB_USER/claude-code-voice/main/uninstall.sh | bash"
7 changes: 7 additions & 0 deletions uninstall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ set -euo pipefail
echo "=== Uninstalling Claude Code Voice ==="
echo ""

# Detect GitHub username from the installed repo before we remove it
_INSTALL_DIR="$HOME/.local/share/claude-code-voice"
_REMOTE_URL=$(git -C "$_INSTALL_DIR" remote get-url origin 2>/dev/null || echo "")
_GITHUB_USER=$(echo "$_REMOTE_URL" | sed -E 's|.*github\.com[:/]([^/]+)/.*|\1|')
_GITHUB_USER="${_GITHUB_USER:-dr-data}"
Comment on lines +7 to +11

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same GitHub username extraction issue as in setup.sh: if the repo’s origin URL isn’t a GitHub URL, the sed substitution leaves the URL unchanged and _GITHUB_USER becomes an invalid path segment for raw.githubusercontent.com, breaking the reinstall command printed at the end. Use a match-only extraction and validate the result before falling back to dr-data.

Copilot uses AI. Check for mistakes.

# 1. Stop and remove launch agents (current + legacy names)
for name in com.claude-code-voice com.claude-code-voice.server com.hebrew-voice.server; do
PLIST="$HOME/Library/LaunchAgents/$name.plist"
Expand Down Expand Up @@ -52,3 +58,4 @@ done
echo ""
echo "=== Uninstall complete ==="
echo "Restart Claude Code for changes to take effect."
echo "Reinstall: curl -fsSL https://raw.githubusercontent.com/$_GITHUB_USER/claude-code-voice/main/setup.sh | bash"
Loading