3 Commits

Author SHA1 Message Date
steve e3f823045c Auto-publish in non-interactive mode in release script
When stdin is not a terminal (e.g. run from a tool or CI), skip the
confirmation prompt and proceed with publishing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:23:28 +01:00
steve eff93e4731 Bump version to 0.2.2
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:22:20 +01:00
steve daa1542672 Add input device selection to interactive config TUI
Enumerates available audio input devices via cpal and presents them
in a dropdown, with "System default" as the first option.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:21:44 +01:00
3 changed files with 57 additions and 2 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "mouth"
version = "0.2.1"
version = "0.2.2"
edition = "2024"
description = "Offline speech-to-text with global hotkey and paste"
license-file = "LICENSE"
+7 -1
View File
@@ -170,7 +170,13 @@ if [ ${#BUILT[@]} -eq 0 ]; then
fi
echo ""
read -rp "Publish release ${TAG} to ${FORGE}? [y/N] " confirm
if [ -t 0 ]; then
read -rp "Publish release ${TAG} to ${FORGE}? [y/N] " confirm
else
# Non-interactive (piped/scripted) — default to yes
confirm="y"
echo "Non-interactive mode: auto-publishing release ${TAG} to ${FORGE}"
fi
if [[ ! "${confirm}" =~ ^[Yy]$ ]]; then
echo "Skipped. Artifacts are in ${RELEASE_DIR}/"
exit 0
+49
View File
@@ -1,4 +1,5 @@
use anyhow::Result;
use cpal::traits::{DeviceTrait, HostTrait};
use dialoguer::{Input, Select};
use std::time::Duration;
@@ -105,6 +106,8 @@ pub fn interactive() -> Result<()> {
.interact()?;
config.audio_feedback = feedback_idx == 0;
config.input_device = prompt_input_device(config.input_device.as_deref())?;
let vad_idx = Select::new()
.with_prompt("VAD (voice activity detection)")
.items(&["enabled", "disabled"])
@@ -122,6 +125,52 @@ pub fn interactive() -> Result<()> {
Ok(())
}
/// Prompt the user to select an audio input device from available devices.
fn prompt_input_device(current: Option<&str>) -> Result<Option<String>> {
let current_label = current.unwrap_or("system default");
let host = cpal::default_host();
let mut device_names: Vec<String> = Vec::new();
if let Ok(devices) = host.input_devices() {
for device in devices {
if let Ok(name) = device.name() {
device_names.push(name);
}
}
}
// Build selection list: "System default" first, then each detected device
let mut items: Vec<String> = vec!["System default".to_string()];
for name in &device_names {
items.push(name.clone());
}
// Find the default selection index
let default_idx = if let Some(cur) = current {
let cur_lower = cur.to_lowercase();
device_names
.iter()
.position(|n| n.to_lowercase().contains(&cur_lower))
.map(|i| i + 1) // offset by 1 for "System default"
.unwrap_or(0)
} else {
0
};
let sel = Select::new()
.with_prompt(format!("Input device (current: {current_label})"))
.items(&items)
.default(default_idx)
.interact()?;
if sel == 0 {
Ok(None)
} else {
Ok(Some(device_names[sel - 1].clone()))
}
}
/// Prompt the user to either press a key combination or type it manually.
fn prompt_hotkey(label: &str, current: &str) -> Result<String> {
let choice = Select::new()