Files
mouth/src/main.rs
T
steve 0cea6a4b28 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>
2026-04-10 22:04:39 +01:00

85 lines
1.8 KiB
Rust

mod audio_feedback;
mod cli;
mod config;
mod coordinator;
mod hotkey;
mod ipc;
mod model_cache;
mod overlay;
mod paste;
mod recorder;
mod shared_state;
mod transcriber;
mod vad;
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "mouth", version, about = "Offline speech-to-text with global hotkey and paste")]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
/// Start the mouth daemon
Run,
/// View or edit configuration
Config {
/// Print current config to stdout
#[arg(long)]
show: bool,
/// Reset config to defaults
#[arg(long)]
reset: bool,
},
/// Manage speech-to-text models
Models {
/// Download the configured model
#[arg(long)]
download: bool,
},
/// Show daemon status, loaded model, and version
Status,
}
fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
)
.init();
let cli = Cli::parse();
match cli.command {
None | Some(Commands::Run) => cli::run_cmd::run(),
Some(Commands::Config { show, reset }) => {
if show {
cli::config_cmd::show()
} else if reset {
cli::config_cmd::reset()
} else {
cli::config_cmd::interactive()
}
}
Some(Commands::Models { download }) => {
if download {
cli::models_cmd::download()
} else {
cli::models_cmd::list()
}
}
Some(Commands::Status) => cli::status_cmd::status(),
}
}