Agent-facing speech to text and text to speech, over Alibaba Model Studio (DashScope).
Offline file transcription: upload, infer, store. There is no realtime mode and no microphone capture — vox transcribes files, up to 12 hours each.
Transcription is content-addressed: the same audio and the same recognition options always resolve to the same run id, and everything after transcription — markdown, subtitles, plain text — is an operation on the stored run. The full surface contract lives in docs/cli-schema.md.
make link # builds bin/vox and symlinks it into ~/.local/binmake build alone leaves the binary in bin/; make unlink removes the
symlink. Installing straight from the module also works:
go install github.com/celados/vox@latestNote that go install writes to GOBIN, which may sit earlier in PATH than
~/.local/bin — make link warns when a copy elsewhere is still winning.
ffmpeg/ffprobe are optional: ffprobe reports duration for non-WAV input
before upload, ffmpeg compresses the TTS cache.
vox auth login dashscope # prompts, validates, then stores the key
vox hear meeting.wav # → YAML envelope with the run id
vox export a3f1c2 --format srt # → subtitles from the stored run
vox say "Hello world" --voice Cherryvox hear recording.wav # transcribe a file
vox hear lecture.mp3 --lang zh # pin the language (biggest quality lever)
vox hear meeting.m4a --speakers # label speakers
vox hear recording.wav --vocab meeting # with a hotword vocabulary
vox hear recording.wav --refresh # re-recognize, overwrite the run
vox session ls # the run index, newest first
vox session ls --file recording.wav # runs for one source
vox session rm a3f1c2 # accepts any unique prefix
vox export a3f1c2 --format md # markdown with frontmatter
vox export a3f1c2 --format srt -o out.srt # subtitles
vox export a3f1c2 --format json # transcript with word timestampshear prints one YAML document. A short transcript is inlined as text; a long
one folds to a preview plus the vox export command that reads the rest, so a
caller never receives an unbounded transcript it did not ask for.
sid: fe3ddc6ebfca
source: ~/recordings/meeting.wav
model: fun-asr
vocab: meeting@e4a2648e
created: 2026-08-01T06:08:57Z
path: ~/.vox/runs/fe3ddc6ebfca
size: { tokens: 54, words: 37, chars: 65, duration: 10 }
text: 第一句话,我们在测试句子切分…Failures print one YAML document to stderr with a closed-set code, so a caller
branches instead of parsing prose:
code: audio_too_large
message: audio is 13h20m, over the 12h limit
hint: split the file into parts under 12 hoursEvery transcription is a file job: the audio is uploaded to Model Studio's free 48-hour temporary store, an async task is submitted, and vox polls it to completion — roughly a minute per 45 minutes of audio. Nothing to host, no credentials beyond the API key.
-m |
Model | Hotword lists | Super hotwords | Diarization |
|---|---|---|---|---|
fun (default) |
fun-asr |
yes | no | yes |
qwen |
qwen-audio-3.0-asr-flash-filetrans |
yes | yes (weight 50) | yes |
Both cap at 12 hours / 2GB and cover Mandarin plus major dialects and ~30 other languages. Sentence boundaries, word timings and per-word confidence come from the service, so subtitles are not guessed from punctuation.
A vocabulary is a YAML file. The CLI never edits it — it only reconciles it with the server-side hotword list.
# ~/.vox/vocabulary/meeting.yaml
lang: zh
default_weight: 4
words:
百炼: 5
Fun-ASR: 5
赛德克巴莱: # empty → default_weight
# Optional: per-model intent. Merged over the base, model block wins.
models:
fun-asr: # keyed by the resolved model id, not the -m alias
words:
声网: 5vox vocab ls # names, paths, per-model sync state, remote quota
vox vocab sync meeting # usually unnecessary — `hear --vocab` syncs on demand
vox vocab prune --dry-run # server-side lists no local file claimsSync is driven by a content hash: unchanged content makes no request, changed
content updates in place so the vocabulary_id survives. Because the resolved
content is part of the run id, editing the YAML produces a new run on the next
hear with no cache-busting flag.
Model-specific API limits are applied by the adapter, not by you: weight: 50
is clamped to 5 on fun, unsupported language codes are dropped, oversized
words are skipped, and each is reported on stderr. The account cap is 10 lists
shared across all models — one vocabulary synced to both models consumes two.
say, voice and cache carry over unchanged and have not been moved onto
runs yet.
vox say "你好世界" --voice Cherry --speed 1.2
vox say "Save this" --output out.wav
vox voice list
vox voice record --file sample.wav --name myvoice
vox cache status| Voice | Gender | Language |
|---|---|---|
| Cherry | Female | zh/en |
| Ethan | Male | zh/en |
| Chelsie | Female | zh/en |
| Serena | Female | zh/en |
| Dylan | Male | zh (Beijing) |
| Jada | Female | zh (Shanghai) |
| Sunny | Female | zh (Sichuan) |
~/.vox/
config.json credentials (0600)
runs/<sid>/ meta.yaml · audio.<ext> · run.json
vocabulary/ <name>.yaml + .index.json
cache/ TTS audio
Issues go to celados/vox — include the command, the run id, and the YAML error document.
MIT