use std::path::PathBuf;
use std::process::ExitCode;
use std::time::Duration;
use clap::{Parser, ValueEnum};
use raqeem_core::{Endpoint, OutputFormat, Transcriber, DEFAULT_COHERE_MODEL};
#[derive(Parser)]
#[command(
name = "raqeem",
version,
about = "رقيم — transcribe Arabic audio via Cohere's open ASR model"
)]
struct Cli {
audio: PathBuf,
#[arg(long, value_enum, default_value_t = ProviderArg::Cohere)]
provider: ProviderArg,
#[arg(long, env = "RAQEEM_API_KEY")]
api_key: Option<String>,
#[arg(long)]
endpoint: Option<String>,
#[arg(long)]
model: Option<String>,
#[arg(long, default_value = "ar")]
lang: String,
#[arg(long, default_value_t = 300)]
timeout: u64,
#[arg(long, value_enum, default_value_t = FormatArg::Text)]
format: FormatArg,
}
#[derive(Clone, Copy, ValueEnum)]
enum ProviderArg {
Cohere,
Openai,
}
#[derive(Clone, Copy, ValueEnum)]
enum FormatArg {
Text,
Json,
}
fn main() -> ExitCode {
let cli = Cli::parse();
match run(cli) {
Ok(out) => {
println!("{out}");
ExitCode::SUCCESS
}
Err(msg) => {
eprintln!("خطأ: {msg}");
ExitCode::FAILURE
}
}
}
fn api_key_for(
provider: ProviderArg,
explicit: Option<String>,
cohere_env: Option<String>,
) -> Option<String> {
match provider {
ProviderArg::Cohere => explicit.or(cohere_env),
ProviderArg::Openai => explicit,
}
}
fn run(cli: Cli) -> std::result::Result<String, String> {
let api_key = api_key_for(
cli.provider,
cli.api_key,
std::env::var("COHERE_API_KEY").ok(),
);
let endpoint = match cli.provider {
ProviderArg::Cohere => {
let key = api_key.ok_or(
"cohere provider needs an API key (--api-key, or $RAQEEM_API_KEY / $COHERE_API_KEY)",
)?;
Endpoint::cohere(key, cli.model)
}
ProviderArg::Openai => {
let url = cli
.endpoint
.ok_or("--provider openai needs --endpoint <url>")?;
let model = cli
.model
.unwrap_or_else(|| DEFAULT_COHERE_MODEL.to_string());
Endpoint::openai_compatible(url, model, api_key)
}
};
let format = match cli.format {
FormatArg::Text => OutputFormat::Text,
FormatArg::Json => OutputFormat::Json,
};
let transcript = Transcriber::with_timeout(endpoint, Duration::from_secs(cli.timeout))
.language(cli.lang)
.transcribe(&cli.audio)
.map_err(|e| e.to_string())?;
Ok(format.render(&transcript))
}
#[cfg(test)]
mod tests {
use super::{api_key_for, ProviderArg};
#[test]
fn cohere_falls_back_to_cohere_env() {
assert_eq!(
api_key_for(ProviderArg::Cohere, None, Some("cohere-key".into())),
Some("cohere-key".into())
);
assert_eq!(
api_key_for(
ProviderArg::Cohere,
Some("explicit".into()),
Some("cohere-key".into())
),
Some("explicit".into())
);
}
#[test]
fn openai_never_uses_cohere_env() {
assert_eq!(
api_key_for(ProviderArg::Openai, None, Some("cohere-key".into())),
None
);
assert_eq!(
api_key_for(
ProviderArg::Openai,
Some("mykey".into()),
Some("cohere-key".into())
),
Some("mykey".into())
);
}
}