v0.2.0: System tray, IPC status, VAD, hotkey grab, and polish

- Add system tray icon with Exit menu (tray-icon/muda)
- Add IPC daemon status via named pipe (Windows) / Unix socket (Linux)
- Add `mouth status` command to query running daemon
- Add daemon lock to prevent multiple instances
- Hide Windows console window when running as daemon
- Wire up Silero VAD model download and speech filtering
- Switch hotkey listener from rdev::listen to rdev::grab to consume hotkeys
- Add hotkey capture mode in interactive config (press keys instead of typing)
- Add all missing key names (brackets, punctuation, numpad, etc.)
- Fix ONNX tensor type mismatches (encoder wants i64, decoder wants i32)
- Add 300ms lead-in silence to compensate for mic startup latency
- Add 300ms trailing recording after stop for speech not to be clipped
- Add 50ms silence before audio feedback blips for device warmup
- Reduce overlay size (150x18, was 200x36)
- Add PolyForm Noncommercial 1.0.0 license
- Flesh out user-focused README
- Update release script with Gitea/GitHub forge support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 22:04:39 +01:00
parent f9d65ff850
commit 0cea6a4b28
19 changed files with 1948 additions and 490 deletions
+116 -32
View File
@@ -1,11 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
# ============================================================
# Release configuration
# ============================================================
REPO="https://gitea.dcglab.co.uk/steve/mouth"
FORGE="gitea" # "gitea" (uses tea CLI) or "github" (uses gh CLI)
# ============================================================
# Derived variables
# ============================================================
VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
RELEASE_DIR="release/v${VERSION}"
BINARY_NAME="mouth"
TAG="v${VERSION}"
echo "=== Mouth Release Build v${VERSION} ==="
echo "=== Mouth Release Build ${TAG} ==="
echo "Forge: ${FORGE} (${REPO})"
echo ""
# Ensure we're in the project root
@@ -14,6 +25,22 @@ if [ ! -f Cargo.toml ]; then
exit 1
fi
# Check CLI tools
if [ "${FORGE}" = "gitea" ]; then
if ! command -v tea &>/dev/null; then
echo "ERROR: 'tea' CLI not found. Install: https://gitea.com/gitea/tea"
exit 1
fi
elif [ "${FORGE}" = "github" ]; then
if ! command -v gh &>/dev/null; then
echo "ERROR: 'gh' CLI not found. Install: https://cli.github.com"
exit 1
fi
else
echo "ERROR: Unknown forge '${FORGE}'. Must be 'gitea' or 'github'."
exit 1
fi
# Clean previous release artifacts for this version
rm -rf "${RELEASE_DIR}"
mkdir -p "${RELEASE_DIR}"
@@ -32,19 +59,12 @@ build_target() {
if cargo build --release --target "${target}" 2>&1; then
local binary="target/${target}/release/${BINARY_NAME}${ext}"
if [ -f "${binary}" ]; then
local archive="${RELEASE_DIR}/${BINARY_NAME}-v${VERSION}-${target}"
local archive="${RELEASE_DIR}/${BINARY_NAME}-${TAG}-${target}"
if [ -n "${ext}" ]; then
# Windows: zip
local zip_name="${archive}.zip"
zip -j "${zip_name}" "${binary}" 2>/dev/null || {
# Fallback if zip not installed
cp "${binary}" "${archive}${ext}"
echo " -> ${archive}${ext}"
BUILT+=("${archive}${ext}")
return
}
echo " -> ${zip_name}"
BUILT+=("${zip_name}")
# Windows: ship the exe directly
cp "${binary}" "${archive}${ext}"
echo " -> ${archive}${ext}"
BUILT+=("${archive}${ext}")
else
# Linux/macOS: tar.gz
local tar_name="${archive}.tar.gz"
@@ -71,30 +91,15 @@ build_target() {
build_target "x86_64-unknown-linux-gnu" "Linux x86_64"
# Windows x86_64 (MSVC target via cargo-xwin)
# ort requires the MSVC target — the GNU/MinGW target has no prebuilt
# ONNX Runtime binaries. cargo-xwin cross-compiles using the MSVC
# toolchain from Linux without needing a Windows machine.
#
# Install once:
# cargo install cargo-xwin
# rustup target add x86_64-pc-windows-msvc
#
if command -v cargo-xwin &>/dev/null && rustup target list --installed | grep -q x86_64-pc-windows-msvc; then
echo "--- Building Windows x86_64 (x86_64-pc-windows-msvc via cargo-xwin) ---"
if cargo xwin build --release --target x86_64-pc-windows-msvc 2>&1; then
local_binary="target/x86_64-pc-windows-msvc/release/${BINARY_NAME}.exe"
if [ -f "${local_binary}" ]; then
archive="${RELEASE_DIR}/${BINARY_NAME}-v${VERSION}-x86_64-pc-windows-msvc"
zip_name="${archive}.zip"
zip -j "${zip_name}" "${local_binary}" 2>/dev/null || {
cp "${local_binary}" "${archive}.exe"
echo " -> ${archive}.exe"
BUILT+=("${archive}.exe")
}
if [ -f "${zip_name}" ]; then
echo " -> ${zip_name}"
BUILT+=("${zip_name}")
fi
archive="${RELEASE_DIR}/${BINARY_NAME}-${TAG}-x86_64-pc-windows-msvc.exe"
cp "${local_binary}" "${archive}"
echo " -> ${archive}"
BUILT+=("${archive}")
else
echo " WARN: Binary not found"
FAILED+=("Windows x86_64 (MSVC)")
@@ -149,3 +154,82 @@ if [ ${#BUILT[@]} -gt 0 ]; then
cat checksums-sha256.txt
cd - > /dev/null
fi
# ============================================================
# Publish release
# ============================================================
if [ ${#BUILT[@]} -eq 0 ]; then
echo ""
echo "No successful builds — skipping release publish."
exit 1
fi
echo ""
read -rp "Publish release ${TAG} to ${FORGE}? [y/N] " confirm
if [[ ! "${confirm}" =~ ^[Yy]$ ]]; then
echo "Skipped. Artifacts are in ${RELEASE_DIR}/"
exit 0
fi
# Ensure the git tag exists
if ! git rev-parse "${TAG}" &>/dev/null; then
echo "Creating git tag ${TAG}..."
git tag -a "${TAG}" -m "Release ${TAG}"
git push origin "${TAG}"
fi
# Collect all release files (artifacts + checksums)
RELEASE_FILES=()
for b in "${BUILT[@]}"; do
RELEASE_FILES+=("${b}")
done
RELEASE_FILES+=("${RELEASE_DIR}/checksums-sha256.txt")
RELEASE_TITLE="Mouth ${TAG}"
RELEASE_BODY="## Mouth ${TAG}
### Downloads
$(for b in "${BUILT[@]}"; do echo "- $(basename "${b}")"; done)
### Checksums (SHA256)
\`\`\`
$(cat "${RELEASE_DIR}/checksums-sha256.txt")
\`\`\`
"
if [ "${FORGE}" = "gitea" ]; then
echo "Publishing to Gitea via tea..."
# Extract host and owner/repo from REPO URL
REPO_OWNER_NAME=$(echo "${REPO}" | sed 's|.*://[^/]*/||')
# Create the release
tea release create \
--repo "${REPO_OWNER_NAME}" \
--tag "${TAG}" \
--title "${RELEASE_TITLE}" \
--note "${RELEASE_BODY}"
# Upload assets
for f in "${RELEASE_FILES[@]}"; do
echo " Uploading $(basename "${f}")..."
tea release asset create \
--repo "${REPO_OWNER_NAME}" \
--tag "${TAG}" \
--name "$(basename "${f}")" \
--file "${f}"
done
elif [ "${FORGE}" = "github" ]; then
echo "Publishing to GitHub via gh..."
gh release create "${TAG}" \
--repo "${REPO}" \
--title "${RELEASE_TITLE}" \
--notes "${RELEASE_BODY}" \
"${RELEASE_FILES[@]}"
fi
echo ""
echo "=== Release ${TAG} published to ${FORGE}! ==="