Skip to main content

Podcast providers

The daily-dispatch CLI makes a two-host, call-in style episode from a day's stories in two steps, and each step is an adapter you pick by name:

  • A script writer turns the stories into a script: anthropic, openai, ollama or command.
  • A voice provider reads the script: elevenlabs, openai, google, polly, azure or local.

ffmpeg then joins the audio into one MP3 next to a Markdown transcript.

Install​

npm install -g @blbaker/daily-dispatch
daily-dispatch --help

The package is not on npm yet. Until it is, build it from the repository and run node packages/daily-dispatch/dist/cli/bin.js wherever this page says daily-dispatch:

pnpm install
pnpm --filter @blbaker/daily-dispatch build

It needs Node.js 22.18 or newer, and ffmpeg on your PATH for audio (brew install ffmpeg, apt install ffmpeg or winget install ffmpeg). A dry run works without ffmpeg.

Make an episode​

daily-dispatch podcast make --stories stories.json \
--writer ollama --voice local --voices voices.json --out episode/

episode/ then holds episode.mp3, script.txt, transcript.md and a chunks/ cache. Run it again after editing script.txt with --script episode/script.txt and only the changed chunks are voiced again.

Paid providers never run without --confirm. Without it, the CLI still runs a free writer so you can read the script first, prints the character count and an estimated cost, and sends nothing to a paid service. Add --dry-run to make sure nothing paid runs even with --confirm.

OptionMeaning
--stories <file>An array of stories, or an edition {"stories": [...]}
--writer <name>anthropic, command, ollama or openai
--voice <provider>azure, elevenlabs, google, local, openai or polly
--voices <file>The voice map, described below
--out <dir>Output directory
--script <file>Voice an existing script and skip the writer
--minutes <n>Target length, default 10
--model, --voice-modelWriter model and voice model
--writer-command <cmd>Command for --writer command, default claude -p
--style-guide <file>Replace the built-in style guide

Each story has headline, source, summary and url. The writer is told that every fact must come from those stories.

Script writers​

NameCallsKeyDefault model
anthropicMessages APIANTHROPIC_API_KEYclaude-sonnet-5-5
openaiChat Completions APIOPENAI_API_KEYgpt-6.1-sol
ollamaLocal /api/chatnoneOLLAMA_MODEL, or llama3.1:8b
commandAny CLI, prompt on stdinnoneclaude -p

anthropic and openai are paid. command counts as free; what it costs is up to the CLI you point it at. When a model breaks a format rule, the writer sends the problems back and asks for a corrected script.

Voice providers​

NameServiceKeyVoice id example
elevenlabsElevenLabs Text-to-DialogueELEVENLABS_API_KEYan ElevenLabs voice id
openaiOpenAI /v1/audio/speechOPENAI_API_KEYash
googleGemini multi-speaker TTSGEMINI_API_KEYKore
pollyAmazon Polly, neural by defaultAWS credentials and AWS_REGIONRuth
azureAzure AI SpeechAZURE_SPEECH_KEY, AZURE_SPEECH_REGIONen-US-AvaMultilingualNeural
localAny OpenAI-compatible TTS server, such as Kokorononethe server's voice name

local talks to http://127.0.0.1:8880/v1 with the model kokoro unless you set LOCAL_TTS_URL and LOCAL_TTS_MODEL. Only ElevenLabs keeps audio tags such as [laughing]; the other providers drop them before sending.

Prices, sources and a per-episode estimate for each provider are on the Daily episode page.

The voice map​

voices.json maps each speaker tag in the script to a voice:

{
"S1": { "id": "voice-id", "name": "Host 1" },
"S2": { "id": "voice-id", "name": "Host 2" },
"CALLER_A": { "id": "voice-id", "name": "Caller A" }
}

id is the provider's name for the voice and name appears in the transcript. S1 and S2 are the hosts and any other tag is a caller, unless you set "role". The package ships examples for ElevenLabs and OpenAI in packages/daily-dispatch/examples/. A real run refuses a map that still has the placeholder ids.

Environment variables​

API keys are read only from the environment, never from a flag or a config file.

VariableUsed by
ANTHROPIC_API_KEY--writer anthropic
OPENAI_API_KEY--writer openai, --voice openai
ELEVENLABS_API_KEY--voice elevenlabs
GEMINI_API_KEY--voice google
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_REGION--voice polly
AZURE_SPEECH_KEY, AZURE_SPEECH_REGION--voice azure
LOCAL_TTS_URL, LOCAL_TTS_MODEL, LOCAL_TTS_API_KEY--voice local
OLLAMA_HOST, OLLAMA_MODEL--writer ollama, default 127.0.0.1:11434
DAILY_DISPATCH_WRITER_COMMANDDefault for --writer-command
LISTENER_NAME, LISTENER_TOWNWho the hosts say sent the links
FFMPEG_PATH, FFPROBE_PATHffmpeg and ffprobe when they are not on PATH

Add a provider​

A voice provider is one file in packages/daily-dispatch/src/voices/ plus one line in that folder's registry.ts. A script writer is one file in src/writers/ plus one line in its registry. The package README has the two interfaces and a worked example of each. Tests pass a fake fetch and check the request, so no test calls a real service.

What has been tested​

Every adapter is tested against recorded and faked HTTP responses. The ollama writer and the local voice have also made a full episode end to end with a local model and a local TTS server. No paid service was called while building the CLI, so the request shapes for Anthropic, OpenAI, ElevenLabs, Gemini, Polly and Azure come from their API documentation and have not been tried against the live services.