Skillshub elevenlabs-local-dev-loop
install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/jeremylongshore/claude-code-plugins-plus-skills/elevenlabs-local-dev-loop" ~/.claude/skills/comeonoliver-skillshub-elevenlabs-local-dev-loop && rm -rf "$T"
manifest:
skills/jeremylongshore/claude-code-plugins-plus-skills/elevenlabs-local-dev-loop/SKILL.mdsource content
ElevenLabs Local Dev Loop
Overview
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.
Prerequisites
- Completed
setupelevenlabs-install-auth - Node.js 18+ with npm/pnpm
for testing (recommended)vitest
Instructions
Step 1: Project Structure
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
Step 2: Environment Configuration
// 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 }; }
Step 3: Mock the SDK for Unit Tests
// 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, }, }), }, };
Step 4: Development Scripts
{ "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" } }
Step 5: Quota-Aware Development
// 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);
Step 6: Integration Test Guard
// 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(); }); });
Output
- Working development environment with hot reload via
tsx watch - Mock layer that avoids API calls and character charges during dev
- Quota checker to prevent surprise billing
- Integration test guard pattern (
)ELEVENLABS_INTEGRATION=1 - Environment-aware model selection (cheap in dev, quality in prod)
Error Handling
| Error | Cause | Solution |
|---|---|---|
| SDK not installed | |
| Mock returns undefined | Mock not wired | Check vi.mock path matches import |
| Integration test fails | No API key | Set in |
| Quota exceeded in dev | Running real API calls | Use mock layer; run first |
Resources
Next Steps
See
elevenlabs-sdk-patterns for production-ready code patterns.