3a0959f55d
Latency: replies are spoken sentence-by-sentence off the desk API's NDJSON endpoint, so the wait is time-to-first-sentence rather than the whole model call, and Deepgram's live websocket transcribes while you're still talking instead of uploading the WAV afterwards. Both fall back invisibly — a stream that fails before anything was said drops to converse(), and a socket that never opens just means the old one-shot path. Speaking lived in four near-copies in the controller (a reply, a holding line, a streamed sentence, a dialogue scene) that had already drifted: one didn't arm barge-in, another skipped the follow-up rule. It's now speech.Speaker plus an Utterance describing the policy differences, with collaborators injected so the whole of it tests without Qt or audio. The mouth follows the audio rather than a timer: tts.level_of reduces each PCM frame to a 0..1 loudness on a sqrt curve (speech sits well below peak, and a linear map leaves the mouth barely open during normal talking) and that indexes the talking frames, which the sprite script now draws as an openness ramp. Offline pyttsx3 has no waveform, so stale levels hand control back to the timed loop instead of freezing the mouth mid-syllable. Also: the pet starts where you left it (ignoring positions on monitors that are no longer connected, since restoring those faithfully is how it ends up somewhere unreachable), and `python -m bolt_pet --doctor` is a preflight that says what to do about each problem rather than only what's wrong. tests/test_pipeline_smoke.py breaks the pure-logic rule on purpose. Every unit test passed all week while notifications sat unspoken for minutes, the pet said things twice and [laughing] got read aloud — each an interaction between two individually-correct units. It drives whole turns against a real HTTP server on a loopback port, faking only the mic and the speakers. It found a NameError in the paint path that would have fired on every repaint while talking. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
232 lines
13 KiB
Bash
232 lines
13 KiB
Bash
# Copy this file to .env and fill in your values.
|
|
|
|
# ── Bolt server (required) ──────────────────────────────────────────────────
|
|
# The same server URL + key desk_client/bolt_desk.py and the Android app use.
|
|
# DESK_API_KEY is the master key from the server's own .env, or a per-user
|
|
# key minted via the api_key_generate desk marker (see the main repo's
|
|
# CLAUDE.md "Per-user API keys" section).
|
|
BOLT_SERVER_URL=http://your-server:5002
|
|
DESK_API_KEY=
|
|
|
|
# Unique per machine/install so sessions don't collide. Defaults to
|
|
# "pet-<hostname>" if unset.
|
|
#DESK_SESSION_ID=pet-my-desktop
|
|
|
|
# ── Wake word ────────────────────────────────────────────────────────────────
|
|
# openWakeWord model trained for "thunderbolt" — thunderbolt.onnx ships in
|
|
# the project root next to this file. Point WAKE_MODEL_FILE at a different
|
|
# .onnx model to change the phrase (same convention as desk_client's
|
|
# WAKE_MODEL_FILE / bolt.onnx in the main repo).
|
|
#WAKE_MODEL_FILE=thunderbolt.onnx
|
|
#WAKE_WORD_THRESHOLD=0.5
|
|
#WAKE_CHECK_INTERVAL_SECONDS=1.2
|
|
|
|
# ── STT (Deepgram) ──────────────────────────────────────────────────────────
|
|
DEEPGRAM_API_KEY=
|
|
#DEEPGRAM_MODEL=nova-3
|
|
|
|
# ── TTS (ElevenLabs) — omit to use offline TTS only ─────────────────────────
|
|
ELEVENLABS_API_KEY=
|
|
ELEVENLABS_VOICE_ID=
|
|
#ELEVENLABS_MODEL_ID=eleven_flash_v2
|
|
#TTS_SAMPLE_RATE=24000
|
|
|
|
# Ask Bolt to use a different voice (or another language) and the server
|
|
# picks one from the ElevenLabs voice library and tags the reply with it.
|
|
# It only tags one reply, and it can't remember the id afterwards — so the
|
|
# pet keeps using that voice until a new one is picked or you choose "Use
|
|
# default voice" in the tray. VOICE_STICKY=false makes each pick last for
|
|
# exactly the one reply it came with instead.
|
|
#VOICE_STICKY=true
|
|
|
|
# ── Multi-voice dialogue (ElevenLabs Text to Dialogue) ──────────────────────
|
|
# Lets Bolt play a short scene in several voices with delivery tags the v3
|
|
# model acts on ("[cheerfully] Hello", "[whispering] He is lying"), driven by
|
|
# the server through a relayed `dialoguectl` command. Name the cast here —
|
|
# "self" always means whatever voice the pet is currently using.
|
|
#DIALOGUE=true
|
|
#DIALOGUE_MODEL_ID=eleven_v3
|
|
#DIALOGUE_VOICES=narrator:9BWtsMINqrJLrRacOk9x,villain:IKne3meq5aSn9XLyUdCD
|
|
|
|
# ── Self-restart (optional) ─────────────────────────────────────────────────
|
|
# `petctl self_restart <why>` lets Bolt reload the pet after editing its own
|
|
# code, so he can check the change live. The code is import-checked first, the
|
|
# restart waits for the current turn to finish, and the reason is carried
|
|
# across so the new process reports back. The guard refuses more than
|
|
# SELF_RESTART_MAX restarts within SELF_RESTART_WINDOW_SECONDS.
|
|
#SELF_RESTART=true
|
|
#SELF_RESTART_MAX=5
|
|
#SELF_RESTART_WINDOW_SECONDS=900
|
|
# Used instead of ELEVENLABS_MODEL_ID whenever the server picked the voice
|
|
# or the reply has non-ASCII in it — the flash_v2 default is English-only.
|
|
#ELEVENLABS_MULTILINGUAL_MODEL_ID=eleven_flash_v2_5
|
|
|
|
# ── Audio devices (optional — leave blank for the system default) ──────────
|
|
#MIC_DEVICE=
|
|
#SPEAKER_DEVICE=
|
|
|
|
# ── VAD tuning (optional) ───────────────────────────────────────────────────
|
|
#VAD_RMS_THRESHOLD=300
|
|
#VAD_SILENCE_END_SEC=1.2
|
|
#VAD_MAX_UTTERANCE_SECONDS=15
|
|
#VAD_MIN_UTTERANCE_SECONDS=0.4
|
|
#VAD_GRACE_SECONDS=4 # how long to wait for you to start talking
|
|
|
|
# ── Follow-up listening (optional) ──────────────────────────────────────────
|
|
# When a reply ends on a question, the pet keeps listening for your answer
|
|
# instead of dropping back to idle and making you say the wake word again.
|
|
# FOLLOW_UP_MAX_TURNS caps how many question-and-answer rounds can chain
|
|
# without you re-triggering it (0 = no cap) — a stop on runaway loops if the
|
|
# server ends every reply with "?" and the mic keeps feeding it noise.
|
|
#FOLLOW_UP_LISTEN=true
|
|
#FOLLOW_UP_MAX_TURNS=3
|
|
#FOLLOW_UP_GRACE_SECONDS=7 # longer than VAD_GRACE_SECONDS: you were asked something
|
|
|
|
# ── Pet window (optional) ───────────────────────────────────────────────────
|
|
#PET_SIZE=160
|
|
#PET_START_X=
|
|
#PET_START_Y=
|
|
#PET_ALWAYS_ON_TOP=true
|
|
#IDLE_ANIMATION_FPS=6
|
|
|
|
# ── Wandering (optional) — the pet strolls to a random spot while idle ───────
|
|
#PET_WANDER=true
|
|
#PET_WANDER_INTERVAL_SECONDS=45 # average pause between strolls (randomized 0.5x-1.5x)
|
|
#PET_WANDER_SPEED=90 # pixels per second
|
|
#PET_WANDER_MAX_DISTANCE=600 # cap on a single stroll's length
|
|
#PET_WANDER_MARGIN=20 # keep this far off the screen edges
|
|
|
|
# ── Mouse behaviour (optional) ──────────────────────────────────────────────
|
|
# Shaped input = the square window's transparent corners stop eating clicks.
|
|
# Click-through = the pet ignores the mouse entirely (control it from the tray).
|
|
#PET_SHAPED_INPUT=true
|
|
#PET_CLICK_THROUGH=false
|
|
#PET_EDGE_SNAP=true
|
|
#PET_SNAP_MARGIN=48
|
|
# Start where you last dragged it. A position on a monitor that's no longer
|
|
# connected is ignored, so unplugging a screen can't hide the pet off-desktop.
|
|
#PET_REMEMBER_POSITION=true
|
|
|
|
# ── Barge-in (optional) — interrupt the pet mid-sentence ────────────────────
|
|
# BARGE_IN_MODE decides what counts as an interruption:
|
|
# wake — only the wake word cuts playback (default). Background noise,
|
|
# coughs and the TV can't stop it mid-sentence.
|
|
# energy — any sustained noise above BARGE_IN_RMS_THRESHOLD does. Faster to
|
|
# trigger, but interrupts on anything loud. That threshold defaults
|
|
# to 4x VAD_RMS_THRESHOLD because the mic also hears the pet's own
|
|
# voice out of the speakers; raise it if playback self-interrupts.
|
|
#BARGE_IN=true
|
|
#BARGE_IN_MODE=wake
|
|
#BARGE_IN_RMS_THRESHOLD=1200 # energy mode only
|
|
#BARGE_IN_FRAMES=4 # energy mode only
|
|
# Wake mode only. Blank tracks the live WAKE_WORD_THRESHOLD (tray tuner);
|
|
# set a number to make interrupting harder than waking the pet from idle,
|
|
# e.g. if Bolt's own voice occasionally trips the model.
|
|
#BARGE_IN_WAKE_THRESHOLD=0.6
|
|
|
|
# ── Auto-update (optional) ──────────────────────────────────────────────────
|
|
# Watches the Gitea releases page for a tag newer than bolt_pet.__version__,
|
|
# then `git checkout`s it and restarts — only ever between turns, never
|
|
# mid-conversation. Requires the install to be a git clone; a working tree
|
|
# with local changes is skipped (never stashed), and any failure after the
|
|
# checkout rolls back to the ref that was live before.
|
|
#AUTO_UPDATE=true
|
|
#UPDATE_REPO_API=https://git.themajesticnetwork.com/api/v1/repos/TheMajesticNetwork/Bolt-Pet
|
|
#UPDATE_CHECK_INTERVAL_SECONDS=3600
|
|
#UPDATE_GIT_REMOTE=origin
|
|
#UPDATE_INSTALL_DEPS=true
|
|
# Only needed if the repo is private (a Gitea token with read:repository).
|
|
#UPDATE_TOKEN=
|
|
|
|
# ── Streaming TTS (optional) — starts talking on the first chunk ────────────
|
|
#TTS_STREAMING=true
|
|
|
|
# ── Latency: streaming the reply and the transcript ─────────────────────────
|
|
# STREAMING_REPLIES speaks each sentence as the server generates it, instead of
|
|
# waiting out the whole model call before the first word. STT_STREAMING sends
|
|
# mic frames to Deepgram as you talk, so the transcript is ready the moment you
|
|
# stop. Both fall back to the old path automatically if anything goes wrong.
|
|
# VAD_SILENCE_END_SEC is the other half: it is dead air on every single turn,
|
|
# so 0.8-1.0 feels markedly snappier than the 1.2 default.
|
|
#STREAMING_REPLIES=true
|
|
#STT_STREAMING=true
|
|
|
|
# ── Screen context (optional) ───────────────────────────────────────────────
|
|
# Sends the focused window's title along with what you said, so "what's this
|
|
# error?" has a referent. Text only — no screenshots leave the machine.
|
|
#SCREEN_CONTEXT=true
|
|
|
|
# ── Monitors (optional) ─────────────────────────────────────────────────────
|
|
# Tacks a one-line summary of your screen layout onto each utterance (how
|
|
# many, their sizes, which one the pet is standing on) so Bolt can decide to
|
|
# `petctl jump 2` without asking what you've got plugged in. Costs nothing —
|
|
# the list comes from the UI, nothing is probed per turn.
|
|
#MONITOR_CONTEXT=true
|
|
|
|
# ── Screen text / OCR (optional) ────────────────────────────────────────────
|
|
# Lets Bolt read what's actually on a monitor with `petctl read [n|here|all]`
|
|
# and use it in his reply. Pull-only — nothing is captured unless he asks,
|
|
# and every read is logged.
|
|
#
|
|
# Needs the extras from requirements.txt plus an OCR engine:
|
|
# pip install mss pytesseract && sudo apt install tesseract-ocr
|
|
# or, without sudo:
|
|
# pip install mss rapidocr-onnxruntime
|
|
# mss captures on X11/Windows/macOS but NOT Wayland.
|
|
#
|
|
# This sends the text of a whole screen to the server when used. That's not a
|
|
# new capability — the shell relay could already screenshot and OCR — but it's
|
|
# a far easier one to reach for. SCREEN_TEXT=false removes it entirely.
|
|
#SCREEN_TEXT=true
|
|
#SCREEN_TEXT_MAX_CHARS=4000
|
|
|
|
# ── Quiet hours / do-not-disturb (optional) ────────────────────────────────
|
|
# Comma-separated HH:MM-HH:MM ranges; wrapping past midnight is fine. While
|
|
# napping the pet dims, stops wandering and makes no proactive noise — the
|
|
# wake word and click-to-talk still work.
|
|
#QUIET_HOURS=23:00-08:00
|
|
#DND_ON_FULLSCREEN=true
|
|
|
|
# ── Desktop notification bridge (optional, Linux/D-Bus) ────────────────────
|
|
# Forwards matching desktop notifications to the server so Bolt can react to
|
|
# them. Off by default: each forwarded notification is a converse() round
|
|
# trip. NOTIFICATION_FILTER is a regex over "<app>: <summary> <body>".
|
|
#NOTIFICATION_BRIDGE=false
|
|
#NOTIFICATION_FILTER=build|deploy|calendar
|
|
#NOTIFICATION_MIN_INTERVAL_SECONDS=60
|
|
|
|
# ── Push-to-talk (optional) ─────────────────────────────────────────────────
|
|
# Global hotkey; needs pynput and a session that allows global key hooks
|
|
# (most Wayland sessions don't). Leave blank to disable.
|
|
#PUSH_TO_TALK_HOTKEY=ctrl+alt+space
|
|
|
|
# ── Wake-word tuning (optional) ─────────────────────────────────────────────
|
|
# Scores within this margin below the threshold show up as "near misses" in
|
|
# the tray's wake-word tuner.
|
|
#WAKE_NEAR_MISS_MARGIN=0.2
|
|
#WAKE_NEAR_MISS_LIMIT=40
|
|
|
|
# ── sudo password prompts (optional) ────────────────────────────────────────
|
|
# The pet has no terminal, so a server-relayed `sudo` would block forever on
|
|
# a tty nobody is watching. With this on, bare `sudo` becomes `sudo -A` and
|
|
# the password is collected in a desktop dialog you have to answer — a real
|
|
# askpass binary if one is installed, otherwise a generated zenity/kdialog
|
|
# wrapper in ~/.cache/bolt-pet/askpass.sh.
|
|
# Set it to false if you'd rather Bolt never be able to ask for root: sudo
|
|
# commands then just fail. Read the dialogs — that box is the only thing
|
|
# between "Bolt decided to run sudo" and it running.
|
|
#SUDO_ASKPASS_PROMPT=true
|
|
#SUDO_ASKPASS_HELPER= # blank = auto-detect
|
|
#SUDO_COMMAND_TIMEOUT_SECONDS=180 # long enough for a human to answer
|
|
|
|
# ── File delivery (optional) ────────────────────────────────────────────────
|
|
# The server's deliver_files tool ("send me that report") queues workspace
|
|
# files on this session; the pet fetches and saves them automatically.
|
|
#RECEIVE_FILES=true
|
|
#DELIVERED_FILES_DIR=~/Downloads/Bolt
|
|
|
|
# ── Misc (optional) ──────────────────────────────────────────────────────────
|
|
#COMMAND_TIMEOUT_SECONDS=30
|
|
#HEARTBEAT_INTERVAL_SECONDS=60
|
|
#HISTORY_LIMIT=100
|