Skip to content

feat: voice input (dictation) using Claude CLI speech-to-text - #25

Merged
SergKam merged 2 commits into
mainfrom
feat/voice-input
Jun 18, 2026
Merged

feat: voice input (dictation) using Claude CLI speech-to-text#25
SergKam merged 2 commits into
mainfrom
feat/voice-input

Conversation

@SergKam

@SergKam SergKam commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a microphone toggle to the chat toolbar that dictates speech into the input box, mimicking the Claude Code CLI's /voice.

The CLI's voice dictation is a TUI-only feature and is unreachable from our headless claude -p --input-format stream-json driver (no flag, no stream-json message, no env var exposes it). So rather than delegate, this replicates the CLI's exact wire protocol — reverse-engineered from the v2.1.175 bundle and validated live (handshake + auth accepted by the server).

How it works

  • Endpoint: streams linear16 PCM (16 kHz, mono) to the private WebSocket wss://api.anthropic.com/api/ws/speech_to_text/voice_stream (an Anthropic-proxied Deepgram Nova-3), with the Deepgram query params the CLI sends.
  • Auth: Authorization: Bearer <accessToken> read from ~/.claude/.credentials.json (claudeAiOauth) — the same token and path the CLI uses. Plus the CLI's User-Agent / x-app / anthropic-client-platform headers to pass the Cloudflare edge.
  • Capture: auto-detected system recorder — parecarecordrec (SoX) → ffmpeg — the same fallbacks the CLI uses. Friendly error if none is installed.
  • Protocol: {"type":"KeepAlive"} on open + every 8s; {"type":"CloseStream"} on stop, then drains trailing finals. TranscriptInterim/TranscriptText preview live in the input; TranscriptEndpoint commits the segment.

UX

  • Toolbar mic toggle (after the model switcher). Turns red while recording.
  • Live interim preview replaces in place; finalized segments commit with a trailing space. Click again (or 2-min safety cap) to stop.

Architecture

Follows the codebase convention — a worker thread owns the blocking tungstenite socket and emits VoiceEvents over mpsc; the UI polls via timeout_add_local. No async runtime. New deps: tungstenite (rustls + ring).

⚠️ Caveat (documented in the module)

This is an undocumented endpoint reached with subscription credentials. It can break on any CLI release and is not a supported API; using subscription auth from a third-party client is plausibly against ToS. All brittle assumptions are isolated in src/services/voice.rs. Chosen deliberately by the maintainer over the robust local-Whisper / configurable-command alternatives.

Tests

  • 5 unit tests cover the transcript message parser (interim/final/endpoint/error/malformed).
  • Full suite green (113 tests). Handshake/auth confirmed against the live server.

SergKam added 2 commits June 18, 2026 11:17
Adds a microphone toggle to the chat toolbar that dictates into the input,
mimicking the Claude Code CLI's `/voice`. The CLI's voice is TUI-only and
unreachable from our headless `claude -p` stream-json driver, so we replicate
its wire protocol directly:

- Stream 16kHz mono linear16 PCM to the private WebSocket endpoint
  wss://api.anthropic.com/api/ws/speech_to_text/voice_stream (a Deepgram
  Nova-3 proxy), authenticated with the Claude.ai OAuth token already in
  ~/.claude/.credentials.json — the same token/path the CLI uses.
- Audio captured from an auto-detected system recorder (parec / arecord /
  rec / ffmpeg), the same fallbacks the CLI relies on.
- KeepAlive every 8s, CloseStream + trailing-final drain on stop; interim
  transcripts preview live in the input and commit on TranscriptEndpoint.

Threading follows the codebase convention: a worker thread owns the blocking
tungstenite socket and emits VoiceEvents over mpsc; the UI polls via
timeout_add_local. No async runtime. New deps: tungstenite (rustls/ring).

Caveat (documented in the module): this is an undocumented endpoint reached
with subscription credentials, so it can break on any CLI release and is not
a supported API. All brittle assumptions are isolated in services/voice.rs.

Handshake/auth validated live against the server; 5 unit tests cover the
transcript message parser.
@SergKam SergKam changed the title feat: voice input (dictation) via the CLI's speech-to-text endpoint feat: voice input (dictation) using Claude CLI speech-to-text Jun 18, 2026
@SergKam
SergKam merged commit 2ad69ff into main Jun 18, 2026
2 checks passed
@SergKam
SergKam deleted the feat/voice-input branch June 18, 2026 11:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant