Skillshub assemblyai-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/assemblyai-local-dev-loop" ~/.claude/skills/comeonoliver-skillshub-assemblyai-local-dev-loop && rm -rf "$T"
manifest:
skills/jeremylongshore/claude-code-plugins-plus-skills/assemblyai-local-dev-loop/SKILL.mdsource content
AssemblyAI Local Dev Loop
Overview
Set up a fast, reproducible local development workflow for AssemblyAI transcription and LeMUR projects with mocking, caching, and hot reload.
Prerequisites
- Completed
setupassemblyai-install-auth - Node.js 18+ with npm/pnpm
- TypeScript project with
ortsxts-node
Instructions
Step 1: Project Structure
my-assemblyai-project/ ├── src/ │ ├── assemblyai/ │ │ ├── client.ts # Singleton client │ │ ├── transcribe.ts # Transcription helpers │ │ └── lemur.ts # LeMUR helpers │ └── index.ts ├── tests/ │ ├── transcribe.test.ts │ └── fixtures/ │ └── sample-transcript.json # Cached API response ├── .env.local # Local secrets (git-ignored) ├── .env.example # Template for team └── package.json
Step 2: Dev Scripts
{ "scripts": { "dev": "tsx watch src/index.ts", "test": "vitest", "test:watch": "vitest --watch", "transcribe": "tsx src/assemblyai/transcribe.ts" }, "devDependencies": { "tsx": "^4.7.0", "vitest": "^1.6.0", "dotenv": "^16.4.0" }, "dependencies": { "assemblyai": "^4.8.0" } }
Step 3: Singleton Client with Env Loading
// src/assemblyai/client.ts import 'dotenv/config'; import { AssemblyAI } from 'assemblyai'; let instance: AssemblyAI | null = null; export function getClient(): AssemblyAI { if (!instance) { if (!process.env.ASSEMBLYAI_API_KEY) { throw new Error('ASSEMBLYAI_API_KEY not set. Copy .env.example to .env.local'); } instance = new AssemblyAI({ apiKey: process.env.ASSEMBLYAI_API_KEY, }); } return instance; }
Step 4: Cache Transcription Results for Fast Iteration
// src/assemblyai/transcribe.ts import fs from 'fs'; import path from 'path'; import { getClient } from './client'; import type { Transcript } from 'assemblyai'; const CACHE_DIR = path.join(process.cwd(), '.assemblyai-cache'); export async function transcribeWithCache( audioUrl: string, options: Record<string, any> = {} ): Promise<Transcript> { const cacheKey = Buffer.from(audioUrl + JSON.stringify(options)) .toString('base64url') .slice(0, 32); const cachePath = path.join(CACHE_DIR, `${cacheKey}.json`); // Return cached result in dev if (process.env.NODE_ENV !== 'production' && fs.existsSync(cachePath)) { console.log('[dev] Using cached transcript:', cacheKey); return JSON.parse(fs.readFileSync(cachePath, 'utf-8')); } const client = getClient(); const transcript = await client.transcripts.transcribe({ audio: audioUrl, ...options, }); // Cache for next run fs.mkdirSync(CACHE_DIR, { recursive: true }); fs.writeFileSync(cachePath, JSON.stringify(transcript, null, 2)); console.log('[dev] Cached transcript:', cacheKey); return transcript; }
Step 5: Test with Mocked Responses
// tests/transcribe.test.ts import { describe, it, expect, vi, beforeEach } from 'vitest'; import { AssemblyAI } from 'assemblyai'; // Mock the assemblyai module vi.mock('assemblyai', () => ({ AssemblyAI: vi.fn().mockImplementation(() => ({ transcripts: { transcribe: vi.fn().mockResolvedValue({ id: 'test-transcript-id', status: 'completed', text: 'This is a test transcript.', audio_duration: 30, words: [ { text: 'This', start: 0, end: 200, confidence: 0.99 }, { text: 'is', start: 210, end: 350, confidence: 0.98 }, ], }), get: vi.fn(), list: vi.fn(), }, lemur: { task: vi.fn().mockResolvedValue({ request_id: 'test-lemur-id', response: 'Test summary of the audio.', }), }, })), })); describe('Transcription', () => { it('should transcribe audio and return text', async () => { const client = new AssemblyAI({ apiKey: 'test-key' }); const result = await client.transcripts.transcribe({ audio: 'https://example.com/audio.wav', }); expect(result.status).toBe('completed'); expect(result.text).toContain('test transcript'); expect(result.words).toHaveLength(2); }); it('should run LeMUR task on transcript', async () => { const client = new AssemblyAI({ apiKey: 'test-key' }); const { response } = await client.lemur.task({ transcript_ids: ['test-transcript-id'], prompt: 'Summarize this.', }); expect(response).toContain('summary'); }); });
Output
- Hot-reloading dev server with
tsx watch - Cached transcription results to avoid repeated API calls during dev
- Mocked test suite that runs without API credentials
- Environment variable management with
.env.local
Error Handling
| Error | Cause | Solution |
|---|---|---|
| Missing env file | Copy to |
| Missing dependency | Run |
| Stale cache results | Outdated cache | Delete directory |
| Test timeout | Slow mock setup | Ensure mocks resolve synchronously |
Resources
Next Steps
See
assemblyai-sdk-patterns for production-ready code patterns.