SpeakEasy
Unified TTS

say("Simple text-to-speech for all your projects. Multiple providers, smart caching, and volume control.",
{ provider: 'openai', voice: 'nova' })
Try it now:
speakeasy "Welcome to SpeakEasy! This unified text-to-speech service makes it easy to add voice to your applications" --provider openai --voice nova --rate 180
npm install @arach/speakeasy
Native macOS player

A permanent place for every spoken update

SpeakEasy gives Codex and your command line one menu-bar player for autoplay, queues, scrubbing, playback speed, volume, and a word-synced transcript HUD.

SpeakEasy menu-bar player showing its playback controls and live transcript
One native player
Narration from Codex and the CLI lands in the same lightweight menu-bar app.
Stay in context
Follow every word in the live HUD, then jump back to the originating Codex task.
Local by default
Use the macOS system voice with no account, or opt into your configured cloud provider.

Ready for your Mac and your agents

Native player

  • • macOS 14.0 or later
  • • Apple Silicon or Intel processor
  • • Developer ID signed and Apple notarized

Codex plugin

  • • Bundled SpeakEasy runtime
  • • System voice works without an API key
  • • OpenAI, ElevenLabs, Groq, and Gemini are optional

Start Speaking in Seconds

No complex setup, no boilerplate. Choose your style and start building.

import { say } from '@arach/speakeasy';

// Quick start - one line!
await say('Hello world!');

// With provider
await say('Hello from OpenAI!', 'openai');
await say('Hello from ElevenLabs!', 'elevenlabs');
import { speak, SpeakEasy } from '@arach/speakeasy';

// Full featured with options
await speak('Priority message', {
  provider: 'openai',
  volume: 0.8,
  priority: 'high'
});

// Custom instance
const speech = new SpeakEasy({
  provider: 'elevenlabs',
  volume: 0.6,
  rate: 200
});

await speech.speak('Custom configuration');
# Install globally
npm install -g @arach/speakeasy

# Quick setup - save your API key
speakeasy --set-key elevenlabs YOUR_API_KEY

# Set as default provider
speakeasy --set-default elevenlabs

# Start speaking!
speakeasy "Hello world"

# Or use system voice (no API key needed)
speakeasy "Hello world" --provider system
// ~/.config/speakeasy/settings.json
{
  "providers": {
    "openai": {
      "voice": "nova",
      "apiKey": "sk-..."
    },
    "elevenlabs": {
      "voiceId": "EXAVITQu4vr4xnSDxMaL",
      "apiKey": "sk-..."
    }
  },
  "defaults": {
    "provider": "openai",
    "volume": 0.7,
    "rate": 180
  }
}

Get Started in 3 Steps

npm install -g @arach/speakeasy
+ @arach/speakeasy@0.2.17 Added 1 package in 2.3s
speakeasy --set-key elevenlabs sk_abc123...
✅ ElevenLabs API key saved to config 🎉 You can now use: speakeasy "Hello world" --provider elevenlabs
speakeasy "Hello world!" --provider elevenlabs
🎙️ Speaking with elevenlabs...
No API key? Use --provider system for built-in macOS voices

Have Claude speak to you

Never miss important notifications. Transform Claude Code's silent alerts into intelligent spoken updates.

Tool Permission Request

Trigger: "Claude needs your permission"
What Claude shows:
Claude needs your permission
Audio Result
Loading waveform...
0:00 / 0:00
Visual Context
speakeasy --setup-hooks
Configure Claude Code notifications
⏺ Update(.claude/hooks/notif.ts)
⎿ Updated .claude/hooks/notif.ts with 10 additions
// ~/.claude/hooks/notification.ts
import { SpeakEasy } from '@arach/speakeasy';

const speakEasy = new SpeakEasy({
  provider: 'groq',
  cache: { enabled: true, ttl: '7d' },
  fallbackOrder: ['groq', 'openai', 'system']
});

// Read notification from Claude Code
const notificationType = process.argv[2];
const payload = JSON.parse(await readStdin());

// Extract project context
const projectMatch = payload.transcript_path?.match(/\/([^\/]+)$/);
const projectName = projectMatch?.[1] || 'your project';

// Transform into natural speech
let spokenMessage = payload.message;
if (projectName !== 'your project') {
  spokenMessage = `In ${projectName}, ${payload.message}`;
}

// Speak the notification
await speakEasy.speak(spokenMessage);

// Also copy to clipboard and log
await copyToClipboard(JSON.stringify(payload, null, 2));
logger.info('Notification processed', { type: notificationType, message: spokenMessage });
@arach/hooked

Ready-to-deploy handlers

ClaudeClaude Hooks

Official documentation