Claude-code-plugins-plus-skills elevenlabs-ci-integration
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/elevenlabs-pack/skills/elevenlabs-ci-integration" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-elevenlabs-ci-integration && rm -rf "$T"
manifest:
plugins/saas-packs/elevenlabs-pack/skills/elevenlabs-ci-integration/SKILL.mdsource content
ElevenLabs CI Integration
Overview
Set up CI/CD pipelines that test ElevenLabs integrations without burning character quota on every PR. Uses a two-tier strategy: mocked unit tests on every push, gated integration tests on demand.
Prerequisites
- GitHub repository with Actions enabled
- ElevenLabs API key for integration tests
- npm/pnpm project with vitest configured
Instructions
Step 1: GitHub Actions Workflow
# .github/workflows/elevenlabs-tests.yml name: ElevenLabs Tests on: push: branches: [main] pull_request: branches: [main] jobs: # Tier 1: Always runs — no API key needed, no quota cost unit-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "20" cache: "npm" - run: npm ci - run: npm test -- --coverage env: # Mock mode — no real API calls ELEVENLABS_API_KEY: "sk_test_mock_key_for_ci" # Tier 2: Only on main or manual trigger — uses real API integration-tests: runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' needs: unit-tests steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "20" cache: "npm" - run: npm ci # Check quota before running integration tests - name: Check ElevenLabs quota env: ELEVENLABS_API_KEY: ${{ secrets.ELEVENLABS_API_KEY }} run: | REMAINING=$(curl -s https://api.elevenlabs.io/v1/user \ -H "xi-api-key: ${ELEVENLABS_API_KEY}" | \ jq '.subscription | (.character_limit - .character_count)') echo "Characters remaining: $REMAINING" if [ "$REMAINING" -lt 5000 ]; then echo "::warning::Low ElevenLabs quota ($REMAINING chars). Skipping integration tests." echo "SKIP_INTEGRATION=true" >> $GITHUB_ENV fi - name: Run integration tests if: env.SKIP_INTEGRATION != 'true' env: ELEVENLABS_API_KEY: ${{ secrets.ELEVENLABS_API_KEY }} ELEVENLABS_INTEGRATION: "1" run: npm run test:integration
Step 2: Configure Repository Secrets
# Store API key as GitHub secret (use a test/dev key, NOT production) gh secret set ELEVENLABS_API_KEY --body "sk_your_test_key_here" # Optional: webhook secret for webhook tests gh secret set ELEVENLABS_WEBHOOK_SECRET --body "whsec_your_secret_here"
Step 3: Unit Test with SDK Mock
// tests/unit/tts-service.test.ts import { describe, it, expect, vi, beforeEach } from "vitest"; // Mock the entire SDK — no API calls, no quota usage vi.mock("@elevenlabs/elevenlabs-js", () => ({ ElevenLabsClient: vi.fn().mockImplementation(() => ({ textToSpeech: { convert: vi.fn().mockResolvedValue( new ReadableStream({ start(controller) { controller.enqueue(new Uint8Array([0xFF, 0xFB, 0x90, 0x00])); // MP3 header controller.close(); }, }) ), stream: vi.fn().mockImplementation(async function* () { yield new Uint8Array([0xFF, 0xFB, 0x90, 0x00]); }), }, voices: { getAll: vi.fn().mockResolvedValue({ voices: [ { voice_id: "21m00Tcm4TlvDq8ikWAM", name: "Rachel", category: "premade" }, ], }), }, user: { get: vi.fn().mockResolvedValue({ subscription: { tier: "pro", character_count: 1000, character_limit: 500000 }, }), }, })), })); import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js"; describe("TTS Service", () => { let client: InstanceType<typeof ElevenLabsClient>; beforeEach(() => { client = new ElevenLabsClient(); }); it("should call TTS with correct parameters", async () => { await client.textToSpeech.convert("21m00Tcm4TlvDq8ikWAM", { text: "Test speech", model_id: "eleven_multilingual_v2", voice_settings: { stability: 0.5, similarity_boost: 0.75 }, }); expect(client.textToSpeech.convert).toHaveBeenCalledWith( "21m00Tcm4TlvDq8ikWAM", expect.objectContaining({ text: "Test speech", model_id: "eleven_multilingual_v2", }) ); }); it("should handle voice listing", async () => { const result = await client.voices.getAll(); expect(result.voices).toHaveLength(1); expect(result.voices[0].name).toBe("Rachel"); }); });
Step 4: Integration Test (Gated)
// tests/integration/tts-smoke.test.ts import { describe, it, expect } from "vitest"; const SKIP = !process.env.ELEVENLABS_INTEGRATION; describe.skipIf(SKIP)("ElevenLabs Integration", () => { it("should generate audio from text", async () => { const { ElevenLabsClient } = await import("@elevenlabs/elevenlabs-js"); const client = new ElevenLabsClient(); // Use Flash model + short text to minimize quota usage const audio = await client.textToSpeech.convert("21m00Tcm4TlvDq8ikWAM", { text: "CI test.", // 8 characters = 4 credits (Flash) model_id: "eleven_flash_v2_5", output_format: "mp3_22050_32", }); expect(audio).toBeDefined(); }, 30_000); it("should list voices", async () => { const { ElevenLabsClient } = await import("@elevenlabs/elevenlabs-js"); const client = new ElevenLabsClient(); const { voices } = await client.voices.getAll(); expect(voices.length).toBeGreaterThan(0); }); });
Step 5: Package Scripts
{ "scripts": { "test": "vitest run", "test:watch": "vitest --watch", "test:integration": "ELEVENLABS_INTEGRATION=1 vitest run tests/integration/", "test:ci": "vitest run --coverage --reporter=junit --outputFile=test-results.xml" } }
CI Strategy Summary
| Tier | When | API Key | Quota Cost | Coverage |
|---|---|---|---|---|
| Unit tests | Every push/PR | Mock key | 0 characters | SDK integration patterns |
| Integration | Main + manual | Real test key | ~50 chars | End-to-end TTS verification |
| Quota check | Before integration | Real test key | 0 (GET only) | Prevents surprise billing |
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| Secret not found in CI | Missing repository secret | |
| Integration tests timeout | Slow TTS generation | Increase test timeout to 30s; use Flash model |
| Quota depleted in CI | Too many integration runs | Use quota guard; limit to main branch only |
| Mock drift | SDK API changed | Update mocks when upgrading SDK |
Resources
Next Steps
For deployment patterns, see
elevenlabs-deploy-integration.