Not every agent response is speech friendly. Responses are often too long, and they frequently include details that are useful on screen but awkward out loud: URLs, file links, markdown, code fences, long lists, and other visual-only context.
TL;DR rewrites that text into a short spoken version, sends it to a configured TTS backend, and streams the audio back as soon as bytes are available.
- Send agent text to TL;DR.
- The summarizer creates a short version of what should be said.
- The rewritten text is sent to TTS.
- WAV audio streams back to the client and can start playing immediately.
The daemon exposes an HTTP API and an tldr speak helper. Hook scripts are
included for common agent harnesses so completions can be spoken automatically.
- Python 3.11+
uvffplayfor the streaming playback- A summarizer backend with an OpenAI-compatible chat completions API
- A TTS backend with an OpenAI/MLX-Audio-compatible
/audio/speechAPI, or local MLX audio support on Apple Silicon - Docker, only if you want to run the server in a container
Install the CLI from GitHub:
uv tool install git+https://github.com/sagebynature/tldrFrom a local checkout:
uv tool install .For local Apple Silicon MLX audio profiles, install optional extras:
uv tool install 'tldr[mlx,kokoro] @ git+https://github.com/sagebynature/tldr'Only the tldr command is installed.
Generate user config:
tldr init-config --profile remoteThe generated config is written to:
~/.config/tldr/config.toml
Use --force to replace an existing generated config:
tldr init-config --profile remote --forceFor Apple Silicon local MLX defaults:
tldr init-config --profile apple-localConfig lookup order:
--config /path/to/config.toml./config.toml~/.config/tldr/config.toml- Built-in defaults
The checked-in config.toml uses http://127.0.0.1:9000/v1 for the
OpenAI-compatible summarizer backend and remote TTS profile examples. Update
base_url, api_key, model, voice, and profile names for your backend.
Useful commands:
tldr config-check --config config.tomltldr init-config --profile remote|apple-local [--force]tldr serve --config config.tomltldr health --config config.tomltldr stop --config config.tomltldr install --harness codex|claude|omp|pi|hermestldr speak [--config config.toml] [--server 127.0.0.1] [--port 9200] [--session_id ID] [--summarize true|false] TEXT...
Start the HTTP daemon:
tldr serve --config config.tomlFrom a development checkout:
uv run python -m tldr serve --config config.tomlThe default server listens on:
http://127.0.0.1:9200
OpenAPI docs are available while the server is running:
http://127.0.0.1:9200/docshttp://127.0.0.1:9200/redochttp://127.0.0.1:9200/openapi.json
Check or stop the daemon:
tldr health --config config.toml
tldr stop --config config.tomlhealth and stop locate the daemon through state under state_dir. With
auto_start = true, commands can start the daemon if no live state is found.
Auto-started daemon stdout and stderr go to <state_dir>/daemon.log.
POST /v1/speak returns streamed audio/wav. This example streams the
response directly into ffplay:
curl -sS \
-H 'Content-Type: application/json' \
-d '{"text":"The implementation is complete. I updated the README, verified CLI options, and left server configuration unchanged.","summarize":true}' \
http://127.0.0.1:9200/v1/speak \
| ffplay -nodisp -autoexit -loglevel error -i pipe:0Save audio instead:
curl -sS \
-H 'Content-Type: application/json' \
-d '{"text":"Speak this after summarizing it.","summarize":true}' \
http://127.0.0.1:9200/v1/speak \
--output reply.wavRequest fields:
textrequired, non-empty stringmetadataoptional objectsummarizeoptional boolean, defaulttruetts_profileoptional named TTS profilesummarizer_profileoptional named summarizer profile
Headers set caller and session identity:
X-TTS-CallerX-TTS-Session-Id
Set "summarize": false to send text directly to TTS.
POST /v1/summarize returns summary-only JSON without generating audio:
{"summary":"Short spoken version.","changed":true}It accepts text, optional summarizer_profile, and summary override fields
word_threshold, max_words, temperature, and max_tokens.
The CLI helper posts to the server with curl and pipes streamed WAV audio to
ffplay:
tldr speak --session_id demo "Codex finished the task and the tests passed."Use a specific server:
tldr speak --server 127.0.0.1 --port 9200 "Read this response out loud."Skip summarization:
tldr speak --summarize false "Speak this exact text."--session_id interrupts any previous playback for the same session before
starting the new one.
Install a hook for your agent harness:
tldr install --harness codexSupported harnesses:
codexclaudeomppihermes
Examples:
tldr install --harness claude
tldr install --harness hermesThe installer copies the matching hook into the harness config area and updates
harness settings where needed. Hermes installs
~/.hermes/agent-hooks/tldr/hermes_tts.py, touches ~/.hermes/tts.enabled,
and updates ~/.hermes/config.yaml. Hooks call the local daemon, so start
tldr serve before expecting spoken completions.
Build and run the daemon with Compose:
docker compose up --buildCompose publishes the service on host port 9200, mounts config, and exposes
host model servers to the container:
${TLDR_CONFIG:-./config.docker.toml}:/config/config.toml:ro
9200:9200
host.docker.internal:9000/v1
Use a different config file:
TLDR_CONFIG=./config.toml docker compose up --buildThe container runs the TL;DR server only. Summarizer and TTS model servers must be reachable from inside the container.
make run # uv run python -m tldr serve --config config.toml
make test # typecheck, then unittest
make typecheck # uv run ty check src tests
make lint # ruff check src tests --fix
make format # ruff format src tests
make check # lint, format, typecheck, testUse another config while developing:
make run CONFIG=/path/to/config.tomlmake lint and make format write fixes.
The default logging config lives at src/tldr/logging.conf. Use a custom
logging config from TOML:
[logging]
config_file = "/path/to/logging.conf"Custom logging paths expand ~. Auto-started daemon stdout and stderr are
written to <state_dir>/daemon.log.
