@arach/speakeasy

One API. Every voice.

await say("Text to speech, with nothing in the way.");

One function, every voice: macOS system voices, OpenAI, ElevenLabs, Groq, and Gemini. The system voice needs no key and works out of the box, so the first line you write already makes sound. Want your coding agent to speak its own summaries? That's the Codex companion.

$ bun add @arach/speakeasy
~950 downloads/mo zero runtime dependencies Node ≥ 22.12 or Bun
// the proof

Five providers. One sentence.

The same words, synthesized five ways. Press play — this is the whole pitch.

macOS System fallback Free. No key. Always there.
OpenAI tts-1 · nova · warm & reliable
ElevenLabs voice IDs · premium quality
Groq Orpheus · very fast
Gemini 2.5 Flash TTS
// missing key? rate limit? offline? // → falls back to the system voice. // It always says something.
// the use case

Your agent finished ten minutes ago.
You were making coffee.

Hook SpeakEasy into Claude Code, Cursor, or Codex and your tools speak a one-line summary when they stop. No API key — the system voice is free.

// ~/.claude/settings.json { "hooks": { "Stop": [{ "command": "npx @arach/speakeasy \"$SUMMARY\"" }] } }
"Build completed successfully." system voice
"Claude needs your permission." system voice
"Waiting for your input." system voice

$ npx @arach/speakeasy "it works" — try it. that's the install.

// the engineering

Built like a library, not a landing page.

01It never stays silent.

Automatic fallback walks your provider order — OpenAI down, key missing, offline — and lands on the system voice. Providers →

fallbackOrder: ["openai", "groq", "system"]

02It never says the same thing twice.

SQLite cache keyed on text, voice, and rate. The second call is a file read, not an API bill. Cache →

cache: { enabled: true, ttl: "7d" }

03It knows what matters.

A priority queue with interrupt: an error alert cuts the line; a status update waits its turn. Queue →

speak(msg, { priority: "high", interrupt: true })
// the contract

That's the entire API.

If you can read this block, you can ship it.

import { SpeakEasy } from "@arach/speakeasy"; const speaker = new SpeakEasy({ provider: "openai", openaiVoice: "nova", rate: 180, cache: { enabled: true }, }); await speaker.speak("Hello, world.");
Browse the docs
New
Introducing SpeakEasy for Codex
A Mac app and iPad control surface built on this library. Speak to the task you're in; hear the answer come back.
See it →