TTSBuddy CLI
Convert text to speech from the command line using the TTSBuddy API.
Install
Homebrew (macOS/Linux)
brew install ngelik/tap/ttsbuddy
Go
go install github.com/ngelik/ttsbuddy-cli/cmd/ttsbuddy@latest
Binary download
Download from GitHub Releases and place in your $PATH.
Update
Homebrew
brew upgrade ngelik/tap/ttsbuddy
Go
go install github.com/ngelik/ttsbuddy-cli/cmd/ttsbuddy@latest
Binary
Download the latest release from GitHub Releases and replace the existing binary in your $PATH.
Check version
ttsbuddy version
Quick Start
ttsbuddy auth login
ttsbuddy speak "Hello from TTS Buddy"
ttsbuddy auth status
Interactive login uses an email code and stores one seven-day ttsc_ CLI
session. A new login replaces the prior CLI session. There is no refresh;
login again after expiry.
# 1. Set your API key (create one in Dashboard -> Settings)
ttsbuddy config set key ttsb_your_key_here
# 2. Convert text to speech
ttsbuddy speak "Hello, world!"
# 3. Listen to the output
afplay ttsbuddy-*.mp3 # macOS
Try It Without Signing Up
The no-signup CLI demo uses fixed sample files and pregenerated audio to prove the CLI workflow without exposing an unrestricted public TTS endpoint.
Run the same constrained demo locally:
./demo/cli-demo.sh
The script builds ttsbuddy when needed and uses only the public demo key and allowlisted sample inputs. See demo/README.md for the exact boundary and manual commands.
Authentication
Use ttsbuddy auth login for interactive terminal work. Use a permanent
ttsb_ key for CI and unattended automation. Prepaid access passes use ttsp_
credentials and are intended for explicit, self-contained access without a
subscription key. Login/logout store the CLI session separately and never
overwrite api_key. Effective credential precedence is --key >
TTSBUDDY_ACCESS_PASS > TTSBUDDY_API_KEY > stored prepaid access pass >
active CLI session > stored permanent key.
ttsbuddy auth logout revokes the stored session before clearing it. A network
or server failure retains the local session so the command can be retried.
--local-only skips revocation and warns that server validity may continue
until the absolute seven-day expiry.
API keys are created in Dashboard → Settings at ttsbuddy.com/dashboard. The API Keys guide covers creation, storage, and revocation. Keys use the format ttsb_<public_id>_<secret>.
Three ways to provide your key (in priority order):
- Flag:
-k ttsb_... (leaks to shell history — avoid in shared environments)
- Environment variable:
export TTSBUDDY_API_KEY=ttsb_...
- Config file:
ttsbuddy config set key ttsb_... (stored at ~/.ttsbuddy/config.json)
Use TTSBUDDY_ACCESS_PASS=ttsp_... to provide a prepaid access pass from the
environment. Passes bought through ttsbuddy access buy starter ... are stored
separately from api_key.
For full details on creating and managing keys, see the API Keys guide.
Commands
speak
Convert text to speech. The main command.
# Inline text
ttsbuddy speak "Hello world"
# From file (.md files auto-preprocessed for better narration)
ttsbuddy speak -f article.md -v bf_emma -o article.mp3
# From stdin
cat notes.txt | ttsbuddy speak -
# Custom voice, language, and speed
ttsbuddy speak "Bonjour" -v st_m1 --language fr -s 0.9
ttsbuddy speak "こんにちは" -v st_f2 --language ja
# JSON output (for scripting)
ttsbuddy speak "Hello" --json
# Print URL without downloading
ttsbuddy speak "Hello" --no-download
# Raw MP3 to stdout (pipe to player)
ttsbuddy speak "Hello" -o - | afplay -
Flags:
| Flag |
Description |
-f, --file <path> |
Read text from file |
-v, --voice <id> |
Voice ID (default: st_m1) |
-l, --language <code> |
Supertonic language code (default: en; examples: fr, de, ja, ko, ar) |
-s, --speed <n> |
Speed 0.5–1.5 (default: 1.2) |
-o, --output <path> |
Output file (- for stdout) |
--output-dir <dir> |
Directory for auto-named files (default: .) |
--timeout <duration> |
Poll timeout (default: 10m) |
--raw |
Skip markdown preprocessing |
--no-download |
Print audio URL instead of downloading |
--idempotency-key <key> |
Override auto-generated idempotency key |
Notes:
.md and .markdown files are automatically preprocessed: headings, links, images, and code blocks are stripped for cleaner narration. Use --raw to send verbatim.
- Fast voices (
st_*) support 30+ language modes through --language, use native display names in ttsbuddy voices, and support the full 0.5–1.5 speed range.
- Local input checks use the same UTF-16 unit estimate as the API. Subscription and CLI-session credentials keep the 500,000-unit advisory limit; prepaid access passes use a 100,000-unit per-request limit. Server-side rejection remains authoritative.
- Auto-named files use the pattern
ttsbuddy-YYYYMMDD-HHMMSS-<voice>.mp3.
Fast voice language codes: en, ar, bg, hr, cs, da, nl, et, fi, fr, de, el, hi, hu, id, it, ja, ko, lv, lt, pl, pt, ro, ru, sk, sl, es, sv, tr, uk, vi.
web
Convert a readable webpage to speech.
# Use backend account preferences for voice, language, and speed
ttsbuddy web https://www.ttsbuddy.com/docs/
# Translate the article to Russian with a Fast voice
ttsbuddy web https://www.ttsbuddy.com/docs/ --language ru --voice st_m1
# Print URL without downloading
ttsbuddy web https://www.ttsbuddy.com/docs/ --no-download
web fetches only http and https pages, extracts the readable article text locally, and sends the extracted text plus source URL to the API. If --voice, --language, or --speed are omitted, the backend applies your TTSBuddy account preferences. When the extracted article language differs from the target language, the backend translates the article before speech generation.
Prepaid access passes are direct-text only for now: web rejects ttsp_
credentials before fetching a page. Save or pipe the text yourself and use
ttsbuddy speak -f <file> or ttsbuddy speak -.
web supports the same output and polling flags as speak: --voice, --language, --speed, --output, --output-dir, --timeout, --no-download, and --idempotency-key.
During longer jobs, web shows the local extraction step, backend submission,
queued/processing status, and real provider percentages when the API has them.
When conversion completes, human output includes the job ID plus speech length,
MP3 size, and generation speed when available.
access
Manage prepaid access passes. These commands never auto-buy from speak or
web; purchases are always explicit.
# Public plan list
ttsbuddy access plans
ttsbuddy access plans --json
# Buy the starter pass with an existing funded wallet
ttsbuddy access buy starter --wallet local --max-price 5.00
ttsbuddy access buy starter --wallet cdp --max-price 5.00
# Check or forget the current pass
ttsbuddy access status
ttsbuddy access forget
access buy rejects --key, does not use stored API keys or existing passes
for purchase, and constructs only the signer named by --wallet. The local
wallet reads TTSBUDDY_EVM_PRIVATE_KEY. The cdp wallet reads
CDP_API_KEY_ID, CDP_API_KEY_SECRET, CDP_WALLET_SECRET, and
TTSBUDDY_CDP_EVM_ACCOUNT_ADDRESS. Wallet values are never accepted as CLI
arguments.
access status uses only TTSBUDDY_ACCESS_PASS or the stored access pass and
checks the server even when the local expiry timestamp has passed. access forget is local-only and removes only the exact pass loaded at command start;
it does not revoke, refund, or modify api_key.
voices
List available TTS voices.
# Curated list with Kokoro voices plus Supertonic language modes (always works offline)
ttsbuddy voices
# Full live catalog from API
ttsbuddy voices --all
# JSON output
ttsbuddy voices --json
Voice output includes ID, native display NAME, LANGUAGE, language CODE, and TYPE. Supertonic Fast voices (st_m1-st_m5, st_f1-st_f5) appear once per supported language mode, for example st_m1 appears as Louis under French with code fr and Noah under German with code de. If --all can't reach the live catalog, it falls back to the curated list with a warning.
Kokoro voices use compact provider codes such as a for American English, b for British English, f for French, and z for Chinese. Fast voices use standard language codes from the list above.
status
Check job status. Read-only — does not download audio.
# Check a specific job
ttsbuddy status <job_id>
# Check most recent job
ttsbuddy status
# Poll until complete
ttsbuddy status <job_id> --watch
# JSON output
ttsbuddy status <job_id> --json
config
Show or set configuration.
# Show all config values
ttsbuddy config
# Get a specific value
ttsbuddy config get voice
# Set values
ttsbuddy config set key ttsb_...
ttsbuddy config set voice st_m1
ttsbuddy config set language fr
ttsbuddy config set speed 0.9
ttsbuddy config set timeout 5m
ttsbuddy config set allow_custom_api_url true
Valid keys: key, voice, language, speed, timeout, output_dir, api_url, tts_api_base_url, allow_custom_api_url
version
ttsbuddy version
ttsbuddy version --json
Configuration
File: ~/.ttsbuddy/config.json (0600 permissions in 0700 directory)
{
"api_key": "ttsb_...",
"default_voice": "st_m1",
"default_language": "en",
"default_speed": 1.2,
"output_dir": ".",
"poll_timeout": "10m"
}
Precedence: flags > environment variables > config file > defaults
| Setting |
Env Variable |
Flag |
Default |
| API key |
TTSBUDDY_API_KEY |
-k |
— |
| Access pass |
TTSBUDDY_ACCESS_PASS |
-k on TTS API commands; not access * |
— |
| CLI auth URL |
TTSBUDDY_CLI_AUTH_URL |
— |
https://www.ttsbuddy.com/v1/cli-auth |
| Voice |
TTSBUDDY_VOICE |
-v |
st_m1 |
| Language |
TTSBUDDY_LANGUAGE |
-l, --language |
en |
| Speed |
TTSBUDDY_SPEED |
-s |
1.2 |
| Output dir |
TTSBUDDY_OUTPUT_DIR |
--output-dir |
. |
| Poll timeout |
TTSBUDDY_TIMEOUT |
--timeout |
10m |
| API URL |
TTSBUDDY_API_URL |
— |
(production) |
| Voice catalog API URL |
TTSBUDDY_TTS_API_BASE_URL |
— |
https://tts.api.prod.ttsbuddy.website |
| Allow custom API URL |
TTSBUDDY_ALLOW_CUSTOM_API_URL |
— |
false |
By default, credentialed commands may use the production TTSBuddy hosts or localhost development endpoints. Sending an API key to any other HTTPS API host requires explicit opt-in with ttsbuddy config set allow_custom_api_url true or TTSBUDDY_ALLOW_CUSTOM_API_URL=true.
Global Flags
These work on any command:
| Flag |
Description |
-k, --key <key> |
API key (overrides config/env) |
--json |
JSON output to stdout only, no human output on stderr |
--quiet |
Suppress progress output |
Output Modes
| Mode |
stdout |
stderr |
Default speak |
nothing (file saved to disk) |
spinner, status, "Saved to ...", final stats |
--json |
JSON response |
nothing |
-o - |
raw MP3 bytes |
spinner (if TTY) |
--quiet |
nothing |
nothing |
--no-download |
nothing |
audio URL and final stats |
--json and -o - are mutually exclusive (both write to stdout) — combining them exits with code 2.
Human progress output shows honest stages such as fetching, submitting, queued,
processing, finalizing, and downloading. Percentages appear only when the
backend receives real provider progress. Completed jobs show speech length, MP3
size when known, generation speed, and job ID.
Example human output for a webpage conversion:
Fetching webpage...
Extracted "Top announcements of AWS re:Invent 2025" (10,793 UTF-16 units)
Submitting webpage TTS request...
Queued job fe57968d...
Processing 42%... (1m47s)
https://tts-buddy-history-prod.s3.amazonaws.com/pro/example.mp3?...
Job ID: fe57968d-0958-4ccd-a3e1-d87a7972f01e
Speech length: 14m23s
MP3 size: 14.4 MB
Generation speed: 39 chars/sec
Exit Codes
| Code |
Meaning |
0 |
Success |
1 |
Runtime or API error (auth, provider, download) |
2 |
Usage or config error (bad flags, missing key, validation) |
130 |
Interrupted (Ctrl+C) — job ID printed for resume |
Pipe Examples
# Convert and play immediately (macOS)
ttsbuddy speak "Hello" -o - | afplay -
# Batch convert markdown files
for f in docs/*.md; do
ttsbuddy speak -f "$f" -o "${f%.md}.mp3"
done
# Get audio URL for scripting
ttsbuddy speak "Hello" --no-download --json | jq -r '.audio_url'
Rate Limits
| Limit |
Value |
| POST requests per minute |
1 per API key |
| GET requests per minute |
30 per API key |
| Max text length |
500,000 UTF-16 units for subscriptions; 100,000 UTF-16 units per prepaid pass request |
| Monthly TTS minutes |
Depends on plan |
| Audio URL lifetime |
Temporary — download immediately |
For full API details, see the API Reference.
Troubleshooting
| Error |
Fix |
| "Invalid API key" |
Run ttsbuddy config set key <your-key> |
| "Subscription inactive" |
Reactivate at ttsbuddy.com/billing |
| "Rate limited" |
Wait and retry (automatic with backoff) |
| "Monthly minutes exhausted" |
Upgrade plan or wait for reset |
| "No API access" |
Your plan may not include API access |
| "Text too long" |
Split input into chunks under the credential's UTF-16 unit limit |
| "Prepaid access pass has insufficient remaining units" |
Run an explicit ttsbuddy access buy starter --wallet <local|cdp> --max-price <decimal> |
| Audio file not found |
Files expire based on plan — re-generate |
Development
Running Tests
# Unit tests (deterministic, no network)
make test
# Unit tests with race detector
go test -race ./...
# Coverage report
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out # per-function summary
go tool cover -html=coverage.out # interactive HTML report
# Lint (install with: make tools)
make lint
# Reachable Go dependency and toolchain vulnerabilities
make vuln
# Live API acceptance tests
TTSBUDDY_API_KEY=ttsb_... make test-acceptance
Test Architecture
- Internal packages (
internal/api, internal/config, internal/markdown, internal/access, internal/wallet) use standard Go unit tests with httptest servers — no network or live API needed.
- Command tests (
cmd/) use a subprocess pattern to safely test os.Exit paths and direct os.Stdout/Stderr writes. Each test re-invokes the test binary via TestHelperProcess, capturing real output and exit codes.
- Acceptance tests (
tests/acceptance_test.sh) run the built binary against the live API, gated by TTSBUDDY_API_KEY.
Build
make build # build to bin/ttsbuddy
make tools # install lint, vulnerability, SBOM, and release tools
make release-snapshot # test release build for all platforms
make clean # remove bin/ and dist/
Documentation
License
MIT