1pub mod capture;
8pub mod enroll;
9pub mod install_model;
10pub mod reflect;
11pub mod review;
12pub mod transcribe;
13
14use anyhow::Result;
15use clap::{Parser, Subcommand};
16
17#[derive(Parser)]
19pub struct VoiceCommand {
20 #[command(subcommand)]
22 pub command: VoiceSubcommands,
23}
24
25#[derive(Subcommand)]
27pub enum VoiceSubcommands {
28 Capture(capture::CaptureCommand),
30 Transcribe(transcribe::TranscribeCommand),
32 Reflect(reflect::ReflectCommand),
34 Review(review::ReviewCommand),
36 InstallModel(install_model::InstallModelCommand),
40 Enroll(enroll::EnrollCommand),
43}
44
45impl VoiceCommand {
46 pub async fn execute(self) -> Result<()> {
52 match self.command {
53 VoiceSubcommands::Capture(cmd) => cmd.execute(),
54 VoiceSubcommands::Transcribe(cmd) => cmd.execute(),
55 VoiceSubcommands::Reflect(cmd) => cmd.execute().await,
56 VoiceSubcommands::Review(cmd) => cmd.execute(),
57 VoiceSubcommands::InstallModel(cmd) => cmd.execute(),
58 VoiceSubcommands::Enroll(cmd) => cmd.execute(),
59 }
60 }
61}
62
63#[cfg(test)]
64#[allow(clippy::unwrap_used, clippy::expect_used)]
65mod tests {
66 use super::*;
67
68 use std::path::PathBuf;
69
70 #[test]
71 fn voice_subcommands_capture_variant() {
72 let cmd = VoiceCommand {
73 command: VoiceSubcommands::Capture(capture::CaptureCommand {
74 idle_after: 5,
75 output: None,
76 device: None,
77 }),
78 };
79 assert!(matches!(cmd.command, VoiceSubcommands::Capture(_)));
80 }
81
82 #[test]
83 fn voice_subcommands_transcribe_variant() {
84 let cmd = VoiceCommand {
85 command: VoiceSubcommands::Transcribe(transcribe::TranscribeCommand {
86 wav: PathBuf::from("/tmp/x.wav"),
87 backend: None,
88 model: None,
89 format: None,
90 speaker: None,
91 threshold: None,
92 speaker_model: None,
93 }),
94 };
95 assert!(matches!(cmd.command, VoiceSubcommands::Transcribe(_)));
96 }
97
98 #[test]
99 fn voice_subcommands_reflect_variant() {
100 let cmd = VoiceCommand {
101 command: VoiceSubcommands::Reflect(reflect::ReflectCommand {
102 transcript: Some(PathBuf::from("/tmp/t.jsonl")),
103 session: None,
104 }),
105 };
106 assert!(matches!(cmd.command, VoiceSubcommands::Reflect(_)));
107 }
108
109 #[test]
110 fn voice_subcommands_install_model_variant() {
111 let cmd = VoiceCommand {
112 command: VoiceSubcommands::InstallModel(install_model::InstallModelCommand {
113 dest: None,
114 force: false,
115 variant: install_model::Variant::WhisperTinyEn,
116 }),
117 };
118 assert!(matches!(cmd.command, VoiceSubcommands::InstallModel(_)));
119 }
120
121 #[tokio::test]
122 async fn voice_command_dispatches_install_model_via_execute() {
123 use crate::voice::models::REQUIRED_FILES;
129
130 let tmp = tempfile::TempDir::new().unwrap();
131 for f in REQUIRED_FILES {
132 std::fs::write(tmp.path().join(f), b"placeholder").unwrap();
133 }
134
135 let cmd = VoiceCommand {
136 command: VoiceSubcommands::InstallModel(install_model::InstallModelCommand {
137 dest: Some(tmp.path().to_path_buf()),
138 force: false,
139 variant: install_model::Variant::WhisperTinyEn,
140 }),
141 };
142 cmd.execute()
143 .await
144 .expect("install-model dispatch should succeed on pre-staged dir");
145 }
146}