diff --git a/src/headless.rs b/src/headless.rs index a0af3f3..11810c7 100644 --- a/src/headless.rs +++ b/src/headless.rs @@ -364,7 +364,7 @@ fn run_once(configs: &[config::AgentConfig]) -> Result<(), String> { } /// Remove directory listing that Claude CLI prepends to output. -fn strip_dir_listing(text: &str) -> &str { +pub fn strip_dir_listing(text: &str) -> &str { // Pattern: lines of single words (filenames) at the start, possibly starting with "." let mut end = 0; for line in text.lines() { diff --git a/src/tui.rs b/src/tui.rs index 99210e8..d1dad42 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -176,6 +176,11 @@ impl App { self.ai_scroll = u16::MAX; } OutputEvent::StreamEnd => { + // Strip directory listing that Claude CLI prepends + let clean = crate::headless::strip_dir_listing(&self.ai_output); + if clean.len() != self.ai_output.len() { + self.ai_output = clean.to_string(); + } write_private( &format!("{STATE_DIR}/ai.txt"), self.ai_output.as_bytes(), diff --git a/src/voice/mod.rs b/src/voice/mod.rs index 3ba7792..42568ad 100644 --- a/src/voice/mod.rs +++ b/src/voice/mod.rs @@ -63,18 +63,15 @@ impl VoiceSystem { if text.trim().is_empty() { return; } let audio = match tts::synthesize(&self.config, text) { Ok(data) => data, - Err(e) => { eprintln!("tts error: {e}"); return; } + Err(_) => return, }; - if let Err(e) = tts::play_audio(&audio, &self.config.tts_model) { - eprintln!("audio play error: {e}"); - } + let _ = tts::play_audio(&audio, &self.config.tts_model); } pub fn listen(&self) -> Option { match stt::recognize(&self.config) { Ok(text) if !text.is_empty() => Some(text), - Ok(_) => None, - Err(e) => { eprintln!("stt error: {e}"); None } + _ => None, } } } diff --git a/src/voice/stt.rs b/src/voice/stt.rs index d71ee7a..f456373 100644 --- a/src/voice/stt.rs +++ b/src/voice/stt.rs @@ -71,7 +71,7 @@ fn record_vad() -> Result, String> { let mut speech_count: u32 = 0; let mut total_frames: u32 = 0; - eprintln!(" listening..."); + // Note: no eprintln here — it corrupts TUI alternate screen loop { match rx.recv_timeout(std::time::Duration::from_millis(100)) { diff --git a/src/voice/tts.rs b/src/voice/tts.rs index 456341f..507c3f0 100644 --- a/src/voice/tts.rs +++ b/src/voice/tts.rs @@ -45,9 +45,16 @@ pub fn synthesize(config: &VoiceConfig, text: &str) -> Result, String> { return Err(format!("TTS API error {status}: {body}")); } - resp.bytes() + let bytes = resp.bytes() .map(|b| b.to_vec()) - .map_err(|e| format!("TTS read error: {e}")) + .map_err(|e| format!("TTS read error: {e}"))?; + + // Verify it's actually audio (MP3 starts with ID3 or 0xFF sync) + if bytes.len() < 4 || (bytes[0] != 0xFF && &bytes[0..3] != b"ID3") { + return Err(format!("TTS returned invalid audio ({} bytes)", bytes.len())); + } + + Ok(bytes) } /// Play audio bytes (MP3) using rodio with pitch shift from ai.json.