speak/index.ts
2026-03-19 14:28:01 -07:00

55 lines
1.3 KiB
TypeScript
Executable File

#!/usr/bin/env bun
import { $ } from "bun";
// Parse ~/.env for ElevenLabs credentials
const envText = await Bun.file(`${process.env.HOME}/.env`).text();
const env: Record<string, string> = {};
for (const line of envText.split("\n")) {
const match = line.match(/^([^#=]+)=(.*)$/);
if (match) env[match[1].trim()] = match[2].trim();
}
const apiKey = env.ELEVENLABS_API_KEY;
const voiceId = env.ELEVENLABS_VOICE_ID;
if (!apiKey || !voiceId) {
console.error("Missing ELEVENLABS_API_KEY or ELEVENLABS_VOICE_ID in ~/.env");
process.exit(1);
}
// Grab text from argv (everything after the script name)
const text = Bun.argv.slice(2).join(" ");
if (!text) {
console.error("Usage: speak <text>");
process.exit(1);
}
// Call ElevenLabs TTS API
const response = await fetch(
`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,
{
method: "POST",
headers: {
"xi-api-key": apiKey,
"Content-Type": "application/json",
},
body: JSON.stringify({ text }),
}
);
if (!response.ok) {
console.error(`ElevenLabs API error: ${response.status} ${response.statusText}`);
process.exit(1);
}
// Write MP3 to temp file, play it, clean up
const tmpFile = `${Bun.env.TMPDIR || "/tmp"}/speak-${Date.now()}.mp3`;
await Bun.write(tmpFile, response);
try {
await $`afplay ${tmpFile}`.quiet();
} finally {
await $`rm ${tmpFile}`.quiet();
}