Version 0.2.0.
Bible-grounded counseling, right in the office. Tell Emmaus what you're going through β typed or spoken, Thai or English β and it listens, opens relevant Scripture with you, and walks you to a concrete next step. Every quoted verse is pulled from a local Bible corpus and checked against it before it's shown, so nothing is ever a hallucinated reference.
Named for Luke 24:13-35: two disciples walk a road, crushed and confused; a stranger joins them, listens to their story first, then opens the Scriptures β and their hearts burn within them. That's the shape of every answer here: listen β open the Scriptures β the heart warms β a next step.
Built as a plugin for the BagIdea office β a 3D-scened panel with voice in and voice out, running entirely on your own API keys.
This plugin is free and will never be sold. No account, no subscription, no ads.
| The companion answering | Settings drawer | FX layer |
|---|---|---|
![]() |
![]() |
![]() |
From the Plugins Hub (inside the office): β― β π§© Plugins Hub β Emmaus β Install.
From a GitHub URL (before it's listed on the Hub, or to track a fork): π§© Plugins β paste the
GitHub URL β the office git clones the repo into plugins/emmaus/ and loads it. Reinstalling
picks up whatever the author has pushed since.
Either way, Emmaus needs at least one API key set in the office's own key vault before it can answer anything β see below.
Set these in the office's key vault (never in the plugin itself β a plugin reads keys from the office registry at runtime, it doesn't ship or store its own).
| Key | Required? | Used for |
|---|---|---|
OPENAI_API_KEY |
Required. | The default first brain (counsel), the default voice engine (synthesize, tts.engine: "openai"), and speech-to-text (transcribe β gpt-4o-transcribe β whisper-1 fallback, no Gemini path). Without this key, counsel can still work if you reconfigure brains to Gemini-only, but transcribe and the default voice will not. |
GEMINI_API_KEY |
Optional. | The second/fallback brain (gemini-3.1-pro-preview), and Google voice mode gemini-tts (config.tts.engine:"google", mode:"gemini-tts"). Also the fallback key for cloud-chirp3 if GOOGLE_TTS_API_KEY isn't set. |
GOOGLE_TTS_API_KEY |
Optional. | Google Cloud Chirp 3 HD voices (config.tts.mode:"cloud-chirp3") β a synchronous, non-LLM text-to-speech API, noticeably faster than gemini-tts. Preferred over GEMINI_API_KEY for this one mode; needs texttospeech.googleapis.com enabled and unblocked on the key's Google Cloud project (see Troubleshooting). |
Nothing works with zero keys set. With just OPENAI_API_KEY, every command works using the
defaults (openai brain and voice, OpenAI transcription).
config with no args returns the current settings; a patch merges into what's saved
(plugins/emmaus/data/config.json) and persists across restarts.
Read the current config:
POST /plugin/emmaus/cmd { "cmd": "config", "args": {} }
β { "ok": true, "config": { "brains": [...], "tts": {...} } }
Set a custom brain order (tried in order; the next one is used only if the previous one errors or still returns unusable JSON after one repair round):
POST /plugin/emmaus/cmd { "cmd": "config", "args": { "brains": [
{ "provider": "openai", "model": "gpt-5.5", "effort": "none", "verbosity": "low" },
{ "provider": "gemini", "model": "gemini-3.1-pro-preview", "effort": "low" },
{ "provider": "openai", "model": "gpt-5.4-mini", "effort": "low" }
] } }
Reset the brain order to the shipped default:
POST /plugin/emmaus/cmd { "cmd": "config", "args": { "brains": "default" } }
Switch the voice engine (tts.engine: openai default, or google; tts.mode:
gemini-tts or cloud-chirp3, only used when engine is google):
POST /plugin/emmaus/cmd { "cmd": "config", "args": { "tts": { "engine": "google", "mode": "cloud-chirp3", "voice": "Kore" } } }
POST /plugin/emmaus/cmd { "cmd": "config", "args": { "tts": { "engine": "openai" } } } # back to the default
A partial tts object merges into the saved one, so {"tts":{"engine":"google"}} alone is enough
to switch engines and keep the rest. Bad values (an engine that isn't openai/google, a mode
that isn't gemini-tts/cloud-chirp3, an empty model/voice) are rejected and nothing is
written.
All commands go through POST /plugin/emmaus/cmd with {"cmd": "<name>", "args": {...}}. Args also
accept a bare string for the primary field (e.g. "args": "PHP 4:6-7" for verse).
The core command. Send a situation, get a full pastoral reply.
POST /plugin/emmaus/cmd { "cmd": "counsel", "args": { "situation": "I've been really anxious about work lately, my boss pressures me every day, I'm scared of getting laid off, and I can't sleep at night.", "lang": "en" } }
{
"ok": true,
"summary": "It sounds like you're carrying a heavy burden of anxiety about work...",
"scriptures": [
{ "ref": "Philippians 4:6-7", "ref_id": "PHP 4:6-7", "text": "In nothing be anxious...", "why": "This passage speaks directly to anxiety...", "verified": true }
],
"guidance": ["..."],
"practical_steps": ["Before the presentation, set aside a few moments to quietly pray...", "..."],
"prayer": "Dear God, I come to You feeling so anxious...",
"follow_up": "What is one small thing you can do today to help prepare your heart and mind?",
"safety": { "crisis": false, "abuse": false, "message": "" },
"topics": ["anxiety"]
}A QUESTION (a direct doctrinal/factual question, not a life situation) returns a short answer
instead, with summary: null. Every scriptures[] entry is checked against the local Bible corpus
before being returned (verified: true) β nothing is quoted without being found in the text.
Look up the text of a reference from the local corpus.
POST /plugin/emmaus/cmd { "cmd": "verse", "args": { "ref": "PHP 4:6-7", "lang": "en" } }
β { "ok": true, "ref": "Philippians 4:6-7", "ref_id": "PHP 4:6-7", "text": "...", "translation": "en-web", "found": true }
List counseling topics (with their key verses) and the loaded Bible translations.
POST /plugin/emmaus/cmd { "cmd": "topics", "args": { "lang": "en" } }
β { "ok": true, "topics": [{ "id": "anxiety", "label": "Anxiety", "refs": ["PHP 4:6-7", "1PE 5:7", "ISA 41:10"] }, ...], "translations": [...] }
Broadcast a line of text over the panel's WebSocket β any already-open Emmaus panel says it
through its registered speaker. Text only; no audio is generated by this command (see
synthesize for that).
POST /plugin/emmaus/cmd { "cmd": "speak", "args": { "text": "Thank you so much for sharing that with me.", "lang": "en" } }
β { "ok": true }
Turn text into real audio: one mp3 per chunk (a short first chunk so playback starts fast, then
whole sentences), plus a manifest β cached by content hash, so replaying the same text costs
nothing extra. Engine is whatever config.tts currently points at.
POST /plugin/emmaus/cmd { "cmd": "synthesize", "args": { "text": "The LORD is my shepherd; I shall not want.", "lang": "en", "voice": "warm", "chunk": 0 } }
β { "ok": true, "count": 4, "chunks": [{ "index": 0, "text": "...", "url": "/plugin/emmaus/static/audio/....mp3", "cached": false, "ms": 884 }], "manifestUrl": "..." }
Speech-to-text: OpenAI gpt-4o-transcribe, falling back to whisper-1 if that call fails. Thai
and English both work; pin the language with lang or let it auto-detect.
POST /plugin/emmaus/cmd { "cmd": "transcribe", "args": { "audio": "data:audio/webm;base64,GkXfo...", "mime": "audio/webm", "lang": "th" } }
β { "ok": true, "text": "I've been feeling so tired and anxious about work lately.", "lang": "en", "model": "gpt-4o-transcribe", "ms": 1617 }
For a real recorded file, skip the base64 and POST the raw audio (or multipart) straight to
/plugin/emmaus/transcribe?lang=th:
curl -X POST http://127.0.0.1:8787/plugin/emmaus/transcribe -F "file=@clip.wav;type=audio/wav" -F lang=th
Read or set the counselor's 3D scene state β the panel drives this itself as you talk, but it can also be read or forced from outside.
POST /plugin/emmaus/cmd { "cmd": "scene" }
β { "ok": true, "state": "idle" }
POST /plugin/emmaus/cmd { "cmd": "scene", "args": { "state": "answering" } }
β { "ok": true, "state": "answering" }
States: idle | listening | thinking | answering.
See The config command above.
- Type or speak β write a situation in the text box, or hold the mic (π) and talk; the mic records, sends the clip for transcription, and fills the box for you before asking.
- Emmaus answers in beats, in order, every time: acknowledge how you're feeling β relevant Scripture with context β what it means for your situation β concrete practical steps β a short prayer written for you β one gentle follow-up question that keeps the conversation going.
- Every verse shown is a real citation checked against the local Bible corpus β book, chapter, and verse, never a paraphrase presented as a quote.
- The answer is read aloud automatically through whichever voice engine
config.ttspoints at, chunked so the first words start playing in a couple of seconds rather than waiting for the whole reply. - The scene behind the panel moves with the conversation:
idle β listening β thinking β answering β idle. - Language: leave the selector on AUTO to answer in whatever language you wrote/spoke in, or pin Thai / English.
panel.html loads a core scene (scene.js + world.js) plus four optional lanes β app.js
mounts whichever of these have registered, so a fork can drop any of them without breaking the
core panel:
character.jsβ the counselor companion (Selah the pilgrim tortoise) standing beside the road, with idle/listen/speak states instead of the scene speaking with no visible speaker.fx.jsβ sprite-based visual effects (the scripture-burst shown above), manifest-driven andprefers-reduced-motionsafe.sfx.jsβ ambient and interaction sound cues (footstep, page-turn, chime) that accompany scene transitions.settings.jsβ the settings drawer (β in the top-right) to control Sound, Motion, Character, and Voice, without going through theconfigcommand by hand. Settings persist tolocalStorageand broadcast anemmaus:settingsevent other modules listen for.
"no key" / a brain or voice call fails immediately. Emmaus reads keys from the office's own
key vault (ctx.reg.apiKeys), not from your shell's environment variables β setting
OPENAI_API_KEY in a terminal has no effect on the running office. Set it in the office's key
manager instead. At minimum OPENAI_API_KEY must be set for the default brain order, the default
voice, and transcription to work at all.
403 on Google Cloud Chirp 3 HD (config.tts.mode: "cloud-chirp3"). Two separate blockers can
each produce a 403, and Google's error alternates which one it reports:
SERVICE_DISABLEDβ the Cloud Text-to-Speech API isn't enabled on the key's Google Cloud project yet. Enabletexttospeech.googleapis.comfor that project in the Google Cloud console.API_KEY_SERVICE_BLOCKEDβ the key itself is restricted to other APIs (e.g. a Gemini-only key). Either lift the restriction, or use a separate key scoped to Cloud Text-to-Speech viaGOOGLE_TTS_API_KEY(recommended β keeps your Gemini key untouched).
gemini-tts mode (config.tts.mode: "gemini-tts") doesn't need either of the above β it only
needs GEMINI_API_KEY β but it is noticeably slower to first sound than OpenAI or Chirp 3 HD, so
expect a longer pause before the answer starts speaking.
Mic permission. The first time you use the mic, your browser/webview will prompt for
microphone access; the choice is remembered per origin after that. If you denied it earlier, the
mic button reports a denied state β re-enable microphone access for the office in your OS/browser
privacy settings and reload the panel. If your device has no working microphone, typing still works
exactly the same β the mic is optional, not required.
Emmaus keeps no log of what you ask it. Your situation text is sent, per request, to the brain provider you've configured (OpenAI and/or Gemini) to generate the answer, and β only if you use voice β to the transcription and text-to-speech providers, the same as any API call the office makes on your behalf; nothing is separately written to a question log or history file by this plugin. The only files Emmaus writes to disk are generated answer audio clips, cached by a hash of the text/voice/engine so the same line is never re-synthesized (and never re-billed) β these are audio of the answer, not a record of your question.
Emmaus ships two Bible translations locally (data/bible/) so every verse can be verified against
real text before it's shown:
en-webβ the World English Bible, public domain. No attribution required, no restrictions.th-kjvβ a Thai translation licensed CC BY-NC-ND 4.0 (Attribution, NonCommercial, NoDerivatives). Verbatim verse excerpts, cited, are explicitly permitted by the source's own terms ("You may share and redistribute this Bible translation or extracts from it..."); the license forbids changing the wording and forbids selling the work. Since Emmaus is free and is never sold, this sits well inside the license's non-commercial terms. Attribution: Thai KJV Bible, Β© the copyright holder, used under CC BY-NC-ND 4.0 β seedocs/research/2026-09-25-thai-bible-license.mdfor the full license research, including the path being pursued for a direct license to the Thai Standard Version (THSV11).
If you fork Emmaus and swap in a different translation, check that translation's own license before shipping it β the rules above apply to the corpus bundled with this repo, not to substitutes.
Emmaus is a companion that surfaces Scripture and walks alongside you β it is not a pastor, a licensed counselor, a doctor, or a lawyer, and it never claims to be. For anything medical, legal, or involving immediate danger to yourself or someone else, please talk to a real professional or call for help. In Thailand:
- Mental Health Hotline 1323 β mental health / crisis hotline, 24 hours.
- Social Assistance Center 1300 β abuse/domestic violence assistance, Ministry of Social Development and Human Security, 24 hours.
- 1669 β emergency medical services.
If you're outside Thailand, please reach your local emergency number or crisis line β don't wait. Emmaus will always still offer Scripture and prayer alongside pointing you to real help; it is never meant to be the only place you turn.
Emmaus is built by the BagIdea office team:
- Monanisa β counselor character design (Selah, Lumi, Tobi candidates)
- Pixel β UI/UX direction
- Poppy β voice engineering (TTS engines, listening matrix, chunked playback)
- Kevin β release engineering and deploys
- Yamamoto β sound design (SFX set, ambient loop)
- Shiba β settings drawer (sound, motion, character, voice display)
- Sun β counsel-quality evaluation
- Sahara β Bible-translation license research


