55 lines
1.3 KiB
TypeScript
Executable File
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();
|
|
}
|