Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Configure Deepgram CI/CD integration for automated testing and deployment. Use when setting up continuous integration pipelines, automated testing, or deployment workflows for Deepgram integrations. Trigger: "deepgram CI", "deepgram CD", "deepgram pipeline", "deepgram github actions", "deepgram automated testing".
.claude/skills/jeremylongshore-deepgram-ci-integration/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 48% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 58% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 118% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 71% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 99% | 0% |
Set up CI/CD pipelines for Deepgram integrations with GitHub Actions. Includes unit tests with mocked SDK, integration tests against the real API, smoke tests, automated key rotation, and deployment gates.
DEEPGRAM_API_KEY stored as repository secret@deepgram/sdk and vitest installedyaml# .github/workflows/deepgram-ci.yml name: Deepgram CI on: push: branches: [main] pull_request: branches: [main] env: NODE_VERSION: '20' jobs: unit-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: npm - run: npm ci - run: npm run lint - run: npm run typecheck - run: npm test -- --reporter=verbose # Unit tests use mocked SDK — no API key needed integration-tests: runs-on: ubuntu-latest needs: unit-tests if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: npm - run: npm ci - run: npm run test:integration env: DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }} timeout-minutes: 5 smoke-test: runs-on: ubuntu-latest needs: integration-tests steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: npm - run: npm ci && npm run build - name: Smoke test run: npx tsx scripts/smoke-test.ts env: DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }} timeout-minutes: 2
typescript// tests/integration/deepgram.test.ts import { describe, it, expect, beforeAll } from 'vitest'; import { createClient, DeepgramClient } from '@deepgram/sdk'; const SAMPLE_URL = 'https://static.deepgram.com/examples/Bueller-Life-moves-702702706.wav'; describe('Deepgram Integration', () => { let client: DeepgramClient; beforeAll(() => { const key = process.env.DEEPGRAM_API_KEY; if (!key) throw new Error('DEEPGRAM_API_KEY required for integration tests'); client = createClient(key); }); it('authenticates successfully', async () => { const { result, error } = await client.manage.getProjects(); expect(error).toBeNull(); expect(result.projects.length).toBeGreaterThan(0); }); it('transcribes pre-recorded audio with Nova-3', async () => { const { result, error } = await client.listen.prerecorded.transcribeUrl( { url: SAMPLE_URL }, { model: 'nova-3', smart_format: true } ); expect(error).toBeNull(); const alt = result.results.channels[0].alternatives[0]; expect(alt.transcript).toContain('Life'); expect(alt.confidence).toBeGreaterThan(0.85); }, 30000); it('returns word-level timing', async () => { const { result } = await client.listen.prerecorded.transcribeUrl( { url: SAMPLE_URL }, { model: 'nova-3' } ); const words = result.results.channels[0].alternatives[0].words; expect(words).toBeDefined(); expect(words!.length).toBeGreaterThan(0); expect(words![0]).toHaveProperty('start'); expect(words![0]).toHaveProperty('end'); expect(words![0]).toHaveProperty('confidence'); }, 30000); it('speaker diarization identifies speakers', async () => { const { result } = await client.listen.prerecorded.transcribeUrl( { url: SAMPLE_URL }, { model: 'nova-3', diarize: true } ); const words = result.results.channels[0].alternatives[0].words; expect(words?.some((w: any) => w.speaker !== undefined)).toBe(true); }, 30000); it('TTS generates audio stream', async () => { const response = await client.speak.request( { text: 'CI test.' }, { model: 'aura-2-thalia-en', encoding: 'linear16', container: 'wav' } ); const stream = await response.getStream(); expect(stream).toBeTruthy(); }, 15000); });
typescript// scripts/smoke-test.ts import { createClient } from '@deepgram/sdk'; const SAMPLE_URL = 'https://static.deepgram.com/examples/Bueller-Life-moves-702702706.wav'; async function smokeTest() { console.log('Deepgram Smoke Test'); console.log('='.repeat(40)); const client = createClient(process.env.DEEPGRAM_API_KEY!); let passed = 0; let failed = 0; // Test 1: Authentication try { const { error } = await client.manage.getProjects(); if (error) throw error; console.log('[PASS] Authentication'); passed++; } catch (err: any) { console.error(`[FAIL] Authentication: ${err.message}`); failed++; } // Test 2: Pre-recorded transcription try { const { result, error } = await client.listen.prerecorded.transcribeUrl( { url: SAMPLE_URL }, { model: 'nova-3', smart_format: true } ); if (error) throw error; if (!result.results.channels[0].alternatives[0].transcript) { throw new Error('Empty transcript'); } console.log('[PASS] Pre-recorded transcription'); passed++; } catch (err: any) { console.error(`[FAIL] Pre-recorded transcription: ${err.message}`); failed++; } // Test 3: TTS try { const response = await client.speak.request( { text: 'Smoke test.' }, { model: 'aura-2-thalia-en' } ); const stream = await response.getStream(); if (!stream) throw new Error('No audio stream'); console.log('[PASS] Text-to-speech'); passed++; } catch (err: any) { console.error(`[FAIL] Text-to-speech: ${err.message}`); failed++; } console.log(`\nResults: ${passed} passed, ${failed} failed`); process.exit(failed > 0 ? 1 : 0); } smokeTest();
json{ "scripts": { "test": "vitest run", "test:watch": "vitest --watch", "test:integration": "vitest run tests/integration/", "test:smoke": "tsx scripts/smoke-test.ts", "lint": "eslint src/ tests/", "typecheck": "tsc --noEmit" } }
typescript// vitest.config.ts import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { globals: true, environment: 'node', include: ['tests/**/*.test.ts'], exclude: ['tests/integration/**'], // Integration tests run separately coverage: { include: ['src/**'], reporter: ['text', 'lcov'], }, }, });
yaml# .github/workflows/rotate-deepgram-key.yml name: Rotate Deepgram API Key on: schedule: - cron: '0 0 1 */3 *' # Quarterly (1st of every 3rd month) workflow_dispatch: jobs: rotate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - name: Rotate key run: | NEW_KEY=$(npx tsx scripts/rotate-key.ts) gh secret set DEEPGRAM_API_KEY --body "$NEW_KEY" echo "Key rotated successfully" env: DEEPGRAM_ADMIN_KEY: ${{ secrets.DEEPGRAM_ADMIN_KEY }} DEEPGRAM_PROJECT_ID: ${{ secrets.DEEPGRAM_PROJECT_ID }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
| Issue | Cause | Resolution | |-------|-------|------------| | Integration tests 401 | Secret not set or expired | Rotate DEEPGRAM_API_KEY secret | | Smoke test timeout | API latency | Increase timeout-minutes | | Tests pass locally, fail in CI | Missing env var | Check secrets are set in repo settings | | Fork PRs can't access secrets | GitHub security | Skip integration tests on fork PRs |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-18 | pass→pass | 10,398 | 5,751 | -45% | 1 | 1 | 0% | 1,963 | 3,640 | +85% | 0 | 0 | — |
case-01 | fail→fail | 19,746 | 17,589 | -11% | 1 | 1 | 0% | 4,480 | 6,607 | +47% | 0 | 0 | — |
case-02 | fail→pass | 15,132 | 11,551 | -24% | 1 | 1 | 0% | 3,555 | 5,267 | +48% | 0 | 0 | — |
case-03 | fail→fail | 13,677 | 9,815 | -28% | 1 | 1 | 0% | 3,158 | 4,773 | +51% | 0 | 0 | — |
case-04 | pass→pass | 25,496 | 13,267 | -48% | 1 | 1 | 0% | 2,500 | 5,416 | +117% | 0 | 0 | — |
case-05 | pass→pass | 13,482 | 13,843 | +3% | 1 | 1 | 0% | 2,748 | 5,357 | +95% | 0 | 0 | — |
case-06 | pass→pass | 15,355 | 15,649 | +2% | 1 | 1 | 0% | 3,073 | 5,590 | +82% | 0 | 0 | — |
case-07 | pass→pass | 15,506 | 10,140 | -35% | 1 | 1 | 0% | 2,797 | 4,471 | +60% | 0 | 0 | — |
case-08 | fail→pass | 12,484 | 4,517 | -64% | 1 | 1 | 0% | 2,153 | 3,407 | +58% | 0 | 0 | — |
case-09 | fail→pass | 8,882 | 5,450 | -39% | 1 | 1 | 0% | 1,624 | 3,535 | +118% | 0 | 0 | — |
case-10 | fail→pass | 14,552 | 11,192 | -23% | 1 | 1 | 0% | 2,859 | 4,898 | +71% | 0 | 0 | — |
case-11 | fail→pass | 9,617 | 5,322 | -45% | 1 | 1 | 0% | 1,785 | 3,561 | +99% | 0 | 0 | — |
case-12 | pass→pass | 12,005 | 8,937 | -26% | 1 | 1 | 0% | 2,430 | 4,623 | +90% | 0 | 0 | — |
case-13 | pass→pass | 12,617 | 7,784 | -38% | 1 | 1 | 0% | 2,527 | 4,095 | +62% | 0 | 0 | — |
case-14 | fail→pass | 15,201 | 13,903 | -9% | 1 | 1 | 0% | 3,038 | 4,614 | +52% | 0 | 0 | — |
case-15 | fail→fail | 14,119 | 6,823 | -52% | 1 | 1 | 0% | 2,698 | 3,848 | +43% | 0 | 0 | — |
case-16 | pass→pass | 14,585 | 10,235 | -30% | 1 | 1 | 0% | 2,787 | 4,533 | +63% | 0 | 0 | — |
case-17 | fail→pass | 10,545 | 5,796 | -45% | 1 | 1 | 0% | 2,113 | 3,650 | +73% | 0 | 0 | — |
case-19 | fail→pass | 9,705 | 3,031 | -69% | 1 | 1 | 0% | 1,917 | 3,161 | +65% | 0 | 0 | — |
case-20 | pass→pass | 10,335 | 7,323 | -29% | 1 | 1 | 0% | 1,843 | 3,888 | +111% | 0 | 0 | — |
case-21 | pass→pass | 7,804 | 4,774 | -39% | 1 | 1 | 0% | 1,458 | 3,486 | +139% | 0 | 0 | — |
case-22 | pass→pass | 13,477 | 9,400 | -30% | 1 | 1 | 0% | 2,398 | 4,520 | +88% | 0 | 0 | — |
case-23 | fail→pass | 11,444 | 6,248 | -45% | 1 | 1 | 0% | 1,989 | 3,756 | +89% | 0 | 0 | — |
case-24 | fail→fail | 8,997 | 6,024 | -33% | 1 | 1 | 0% | 1,802 | 3,657 | +103% | 0 | 0 | — |
case-25 | pass→pass | 6,039 | 2,110 | -65% | 1 | 1 | 0% | 1,070 | 2,946 | +175% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted. The headline lift of +36 percentage points is the difference between those two pass rates over the 25 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.