From elevenlabs-pack
Sets up local ElevenLabs TTS dev loop with SDK mocking for cost-free Vitest testing, env config, hot reload, and audio output validation.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin elevenlabs-packThis skill is limited to using the following tools:
Set up a fast, cost-effective local development workflow for ElevenLabs audio projects. Includes SDK mocking to avoid burning character quota during development, audio output testing, and hot-reload patterns.
Implements production reference architecture for ElevenLabs TTS/voice apps with TypeScript project structure, service layers, caching, API routes, queues, and monitoring.
Build and troubleshoot ElevenLabs TTS integrations in Node/Python/web apps: auth, voice/model selection, streaming vs batch generation, latency, fallbacks, secure API keys.
Sets up local dev environment for Speak language app with mock AI tutors, pronunciation assessment, audio fixtures, and vitest tests. For offline workflow and testing.
Share bugs, ideas, or general feedback.
Set up a fast, cost-effective local development workflow for ElevenLabs audio projects. Includes SDK mocking to avoid burning character quota during development, audio output testing, and hot-reload patterns.
elevenlabs-install-auth setupvitest for testing (recommended)my-elevenlabs-project/
├── src/
│ ├── elevenlabs/
│ │ ├── client.ts # Singleton client wrapper
│ │ ├── config.ts # Environment-aware config
│ │ └── tts.ts # TTS service layer
│ └── index.ts
├── tests/
│ ├── __mocks__/
│ │ └── elevenlabs.ts # SDK mock for free testing
│ ├── tts.test.ts
│ └── fixtures/
│ └── sample.mp3 # Known-good audio for comparison
├── output/ # Generated audio (git-ignored)
├── .env.local # Local API key (git-ignored)
├── .env.example # Template for team
└── package.json
// src/elevenlabs/config.ts
export interface ElevenLabsConfig {
apiKey: string;
modelId: string;
defaultVoiceId: string;
outputFormat: string;
}
export function loadConfig(): ElevenLabsConfig {
const env = process.env.NODE_ENV || "development";
return {
apiKey: process.env.ELEVENLABS_API_KEY || "",
// Use cheaper/faster model in dev, high-quality in prod
modelId: env === "production"
? "eleven_multilingual_v2" // 1.0 credits/char, best quality
: "eleven_flash_v2_5", // 0.5 credits/char, ~75ms latency
defaultVoiceId: process.env.ELEVENLABS_VOICE_ID || "21m00Tcm4TlvDq8ikWAM",
outputFormat: "mp3_22050_32", // Smaller files for dev
};
}
// tests/__mocks__/elevenlabs.ts
// Avoids API calls (and character charges) during testing
import { vi } from "vitest";
import { readFileSync } from "fs";
const sampleAudio = readFileSync("tests/fixtures/sample.mp3");
export const mockElevenLabsClient = {
textToSpeech: {
convert: vi.fn().mockResolvedValue(
new ReadableStream({
start(controller) {
controller.enqueue(sampleAudio);
controller.close();
},
})
),
stream: vi.fn().mockImplementation(async function* () {
yield sampleAudio.subarray(0, 1024);
yield sampleAudio.subarray(1024);
}),
},
voices: {
getAll: vi.fn().mockResolvedValue({
voices: [
{ voice_id: "21m00Tcm4TlvDq8ikWAM", name: "Rachel" },
{ voice_id: "ErXwobaYiN019PkySvjV", name: "Antoni" },
],
}),
},
user: {
get: vi.fn().mockResolvedValue({
subscription: {
tier: "free",
character_count: 500,
character_limit: 10000,
},
}),
},
};
{
"scripts": {
"dev": "tsx watch src/index.ts",
"test": "vitest",
"test:watch": "vitest --watch",
"test:integration": "ELEVENLABS_INTEGRATION=1 vitest run tests/integration/",
"generate": "tsx src/generate.ts",
"quota": "tsx src/check-quota.ts"
}
}
// src/check-quota.ts — run before integration tests
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
const client = new ElevenLabsClient();
async function checkQuota() {
const user = await client.user.get();
const { character_count, character_limit } = user.subscription;
const remaining = character_limit - character_count;
const pct = ((character_count / character_limit) * 100).toFixed(1);
console.log(`Characters: ${character_count.toLocaleString()} / ${character_limit.toLocaleString()} (${pct}% used)`);
console.log(`Remaining: ${remaining.toLocaleString()} characters`);
if (remaining < 1000) {
console.warn("WARNING: Low character quota. Use mocks for development.");
process.exit(1);
}
}
checkQuota().catch(console.error);
// tests/tts.test.ts
import { describe, it, expect, vi, beforeAll } from "vitest";
import { mockElevenLabsClient } from "./__mocks__/elevenlabs";
// Only hit real API when explicitly enabled
const useRealApi = process.env.ELEVENLABS_INTEGRATION === "1";
describe("TTS Service", () => {
it("should generate audio from text (mocked)", async () => {
const audio = await mockElevenLabsClient.textToSpeech.convert(
"21m00Tcm4TlvDq8ikWAM",
{ text: "Test speech", model_id: "eleven_flash_v2_5" }
);
expect(audio).toBeDefined();
});
it.skipIf(!useRealApi)("should generate real audio (integration)", async () => {
const { ElevenLabsClient } = await import("@elevenlabs/elevenlabs-js");
const client = new ElevenLabsClient();
const audio = await client.textToSpeech.convert("21m00Tcm4TlvDq8ikWAM", {
text: "Integration test.",
model_id: "eleven_flash_v2_5",
});
expect(audio).toBeDefined();
});
});
tsx watchELEVENLABS_INTEGRATION=1)| Error | Cause | Solution |
|---|---|---|
MODULE_NOT_FOUND | SDK not installed | npm install @elevenlabs/elevenlabs-js |
| Mock returns undefined | Mock not wired | Check vi.mock path matches import |
| Integration test fails | No API key | Set ELEVENLABS_API_KEY in .env.local |
| Quota exceeded in dev | Running real API calls | Use mock layer; run npm run quota first |
See elevenlabs-sdk-patterns for production-ready code patterns.