use anyhow::Result;
use colored::Colorize;
use oxo_flow_ai::provider;
pub async fn ai_status_command() -> Result<()> {
println!("{}", "AI Status".bold().green().underline());
println!();
let provider = provider::create_provider_from_env();
let name = provider.name();
let model = provider.model().unwrap_or_else(|| "default".into());
if name == "disabled" {
println!(" Status: {}", "DISABLED".yellow().bold());
println!();
println!(" To configure AI, run:");
println!(" {}", "oxo-flow ai setup".bold().cyan());
println!();
println!(" Or manually set environment variables:");
println!(" export OXO_FLOW_AI_PROVIDER=<provider>");
println!(" export <PROVIDER>_API_KEY=sk-...");
return Ok(());
}
println!(" Provider: {}", name.green());
println!(" Model: {}", model);
if let Some(url) = provider.api_url() {
println!(" Endpoint: {}", url.dimmed());
}
println!();
print!(" Connectivity ... ");
match provider.chat("You are helpful.", "Say OK").await {
Ok(_) => println!("{}", "OK".green()),
Err(e) => println!("{} ({})", "FAIL".red(), e),
}
let sessions_dir = oxo_flow_ai::session::sessions_dir();
let count = std::fs::read_dir(&sessions_dir)
.map(|e| e.filter_map(|x| x.ok()).count())
.unwrap_or(0);
println!(" Sessions: {}", count.to_string().cyan());
println!(
" Storage: {}",
sessions_dir.display().to_string().dimmed()
);
println!();
println!(
" Run {} for comprehensive self-test.",
"oxo-flow ai test".bold()
);
Ok(())
}
pub async fn ai_test_command() -> Result<()> {
println!("{}", "AI Self-Test".bold().green().underline());
println!();
let provider = provider::create_provider_from_env();
if provider.name() == "disabled" {
anyhow::bail!("AI is disabled. Run 'oxo-flow ai setup' first.");
}
print!(" [1/3] Connectivity ... ");
match provider
.chat("You are helpful.", "Reply with just the word OK")
.await
{
Ok(r) if r.trim() == "OK" => println!("{}", "PASS".green()),
Ok(r) => println!(
"{} (unexpected: {})",
"WARN".yellow(),
&r[..20.min(r.len())]
),
Err(e) => {
println!("{} ({})", "FAIL".red(), e);
anyhow::bail!("Connectivity test failed — check API key and network");
}
}
print!(" [2/3] Generation ... ");
let test_intent = "a single rule that writes hello world to a file";
let system = r#"Generate a minimal .oxoflow workflow. Output ONLY the TOML inside ```toml fences.
The workflow should be: [workflow] name="test", [[rules]] name="hello", output=["hello.txt"],
threads=1, memory="1G", shell="echo hello > hello.txt".
No explanation, just the TOML."#;
let user_msg = format!("Generate: {test_intent}");
match provider.chat(system, &user_msg).await {
Ok(response) => {
if response.contains("[workflow]") && response.contains("[[rules]]") {
println!("{}", "PASS".green());
} else {
println!("{} (no TOML in response)", "WARN".yellow());
}
}
Err(e) => {
println!("{} ({})", "FAIL".red(), e);
anyhow::bail!("Generation test failed");
}
}
print!(" [3/3] Analysis ... ");
let test_toml = r#"[workflow]
name = "self-test"
[[rules]]
name = "step1"
output = ["out.txt"]
threads = 1
memory = "1G"
shell = "echo ok > out.txt"
"#;
let system = "You are a workflow auditor. Reply ONLY with 'PASS' or 'FAIL: <reason>'.";
let user = format!("Audit this workflow:\n```toml\n{test_toml}\n```");
match provider.chat(system, &user).await {
Ok(response) => {
if response.contains("PASS") {
println!("{}", "PASS".green());
} else {
println!(
"{} ({})",
"WARN".yellow(),
response.lines().next().unwrap_or("")
);
}
}
Err(e) => {
println!("{} ({})", "FAIL".red(), e);
}
}
println!();
println!("{}", "All tests completed.".bold().green());
println!(" Run 'oxo-flow ai' for quick status anytime.");
Ok(())
}
pub async fn ai_setup_command() -> Result<()> {
if !std::io::IsTerminal::is_terminal(&std::io::stdin()) {
println!(
"{}",
"AI Setup — Non-Interactive Mode".bold().green().underline()
);
println!();
println!(" The interactive wizard requires a terminal.");
println!();
println!(" To configure AI non-interactively, set environment variables:");
println!(" export OXO_FLOW_AI_PROVIDER=<deepseek|openai|claude|ollama>");
println!(
" export DEEPSEEK_API_KEY=sk-... # or OPENAI_API_KEY, ANTHROPIC_AUTH_TOKEN"
);
println!();
println!(" Or edit the config file directly:");
println!(" {}", "~/.oxo-flow/ai_config.json".dimmed());
println!();
println!(" See docs: https://traitome.github.io/oxo-flow/documentation/reference/ai-cli/");
return Ok(());
}
println!("{}", "AI Setup Wizard".bold().green().underline());
println!();
println!(" This will configure AI for oxo-flow and save to:");
println!(" {}", "~/.oxo-flow/ai_config.json".dimmed());
println!();
println!("{}", "Step 1: Choose Provider".bold());
println!(" [1] DeepSeek (OpenAI-compatible, recommended)");
println!(" [2] OpenAI / OpenAI-compatible (Groq, Together, Azure, etc.)");
println!(" [3] Anthropic Claude / Anthropic-compatible");
println!(" [4] Ollama (local)");
print!("\n Select [1-4]: ");
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
let choice = input.trim();
let (provider_name, key_env, key_prompt) = match choice {
"1" => ("deepseek", "DEEPSEEK_API_KEY", "Enter DeepSeek API key"),
"2" => ("openai", "OPENAI_API_KEY", "Enter OpenAI API key"),
"3" => ("claude", "ANTHROPIC_AUTH_TOKEN", "Enter Anthropic API key"),
"4" => ("ollama", "", ""),
_ => anyhow::bail!("Invalid choice: {choice}"),
};
let api_key = if choice == "4" {
None
} else {
println!();
println!("{}", "Step 2: API Key".bold());
println!(" {}:", key_prompt);
print!(" ");
let mut key = String::new();
std::io::stdin().read_line(&mut key)?;
let key = key.trim().to_string();
if key.is_empty() {
anyhow::bail!("API key is required");
}
Some(key)
};
println!();
println!(
"{}",
"Step 3: Model (optional, press Enter for default)".bold()
);
print!(" Model name: ");
let mut model = String::new();
std::io::stdin().read_line(&mut model)?;
let model = model.trim().to_string();
let model = if model.is_empty() { None } else { Some(model) };
let key_ref: Option<&str> = api_key.as_deref();
let model_ref: Option<&str> = model.as_deref();
oxo_flow_ai::provider::save_ai_config(provider_name, key_ref, None, model_ref);
println!();
println!(
"{} Configuration saved to ~/.oxo-flow/ai_config.json",
"✓".green()
);
if api_key.is_some() {
println!();
println!(" {} To use in current terminal, run:", "💡".bold());
println!(" export OXO_FLOW_AI_PROVIDER={provider_name}");
println!(" export {key_env}=\"<your-key>\"");
}
print!(" Testing connectivity ... ");
let provider = provider::create_provider_from_env();
match provider.chat("You are helpful.", "Say OK").await {
Ok(_) => println!("{}", "Connected!".green()),
Err(e) => println!("{}: {}", "Failed".red(), e),
}
println!();
println!(
" Run {} for comprehensive self-test.",
"oxo-flow ai test".bold()
);
println!(" Run {} anytime to check status.", "oxo-flow ai".bold());
Ok(())
}