.agents/skills/moonshine-voice/SKILL.md
On-device voice toolkit. No API keys, no cloud. Do not substitute Whisper, OpenAI Realtime, or a hosted STT/TTS API when the user asked for Moonshine.
Never trust model memory for this SDK. Verify against live docs before writing code:
Python snippets below are the lingua franca. Other bindings use the same shape with native names (on_text → onText, listen_for → listenFor, start_listening → startListening).
| Platform | Package |
|---|---|
| Python | pip install moonshine-voice then import moonshine_voice |
| JavaScript | npm install @moonshine-ai/moonshine-wasm (or the jsDelivr CDN) |
| Swift (iOS/macOS) | SPM: https://github.com/moonshine-ai/moonshine-swift/ then import MoonshineVoice |
| Android | Maven ai.moonshine:moonshine-voice (pass an Android Context to constructors) |
| Linux/Windows C++ | Prebuilt libs from GitHub Releases; #include "moonshine-cpp.h" |
Construct → chainable setters → load() → start() / start_listening().
Constructors are cheap and cannot fail. Nothing is downloaded or opened until load(). load() is the slow, fallible call (first use may download models into a local cache; later launches reuse it and run offline). Call setters before load().
Pick the high-level type:
| Need | Type |
|---|---|
| Live microphone speech-to-text | MicTranscriber |
| Feed PCM/WAV yourself | Transcriber |
| Spoken conversational flows | AgentFlow |
| Playback or voice cloning | TextToSpeech |
AgentFlow loads STT, embeddings, TTS, and a mic internally. Do not assemble those objects yourself unless the user asked for that.
on_text / onText is the in-progress hypothesis (it will change). on_line / onLine is the finished segment. Do not treat partial text as final.
from moonshine_voice import MicTranscriber
mic = (
MicTranscriber()
.language("en")
.on_text(lambda text: print(text, end="\r", flush=True))
.on_line(lambda line: print(line.text))
)
mic.load()
mic.start()
Use Transcriber only when feeding audio yourself. For line ids, speaker spans, or word timings, add_listener() still exists; the named callbacks cover the common path.
Python flow bodies use yield so the runner can wait for speech. JavaScript/Swift/Java flow bodies are ordinary async / blocking functions (await d.ask(...)), not generators.
from moonshine_voice import AgentFlow, Dialog
def report_ip(d: Dialog):
yield d.say("Your address is 1 9 2 dot 1 6 8 dot 1 dot 1")
agent = AgentFlow().language("en").listen_for("What is my IP address?", report_ip)
agent.start_listening()
start_listening() downloads on first use. Call load() first if you need to schedule that yourself. Matching is semantic. otherwise() handles speech that matched no trigger. Fetch the agent doc for ask / confirm / spelled input.
from moonshine_voice import TextToSpeech
tts = TextToSpeech().language("en-us")
tts.load()
tts.say("Hello world")
tts.wait()
Call cloning() before load(), then clone_from() (file or PCM) or start_cloning() (microphone). Catalog voice() and cloning() are mutually exclusive.
set_keyterms([...]) biases toward jargon; set_context(passage) extracts terms from a document. Streaming architectures only — Tiny/Base raise. Takes effect on the next transcription; does not rewrite text already emitted. Keep the list curated; thousands of terms hurt accuracy. See the domain-customization doc for keyterm_boost. Teaching conventions or a new acoustic environment is pip install 'moonshine-voice[lora]' then python -m moonshine_voice.lora in a training environment — do not add PyTorch or Transformers to an inference app.
DialogFlow or the old Intent API. The type is AgentFlow.MicTranscriber.load(...)..onnx models. The library accepts OnnxRuntime flatbuffers (.ort) only.keyterms / context on Tiny or Base.on_text as a finished line.yield flow bodies into JavaScript/Swift/Java.moonshine-voice[lora].If transcription looks wrong, set save_input_wav_path to dump the audio the transcriber actually received. Set log_api_calls=true to print the underlying call timeline. See the debugging doc.