//! API key setup — provider selector, key validation, timeout, then verification. use std::thread; use std::time::Duration; use sly::ansi; use sly::block::Block; use sly::color::Color; use sly::component::RunError; use sly::flow::Flow; use sly::input; use sly::style::{Span, Style}; use sly::terminal; use sly::view::View; use sly::widgets::{ checklist::{CheckState, Checklist, ChecklistItem}, grouped_selector::{self, GroupedItem, GroupedSelector}, number_input::{self, NumberInput}, selector::SelectorItem, spinner, text_input::{self, TextInput}, }; fn validate_api_key(s: &str) -> Result<(), String> { let t = s.trim(); if t.is_empty() { return Err("API key is required".into()); } if t.len() < 20 { return Err("API key must be at least 20 characters".into()); } Ok(()) } fn main() { println!("SLY — API Key Setup"); println!("Up/Down + Enter to choose, Escape to cancel"); println!(); let providers: Vec> = vec![ GroupedItem::Header("AI Providers".to_string()), GroupedItem::Item(SelectorItem::new("OpenAI", "openai")), GroupedItem::Item(SelectorItem::new("Anthropic", "anthropic")), GroupedItem::Item(SelectorItem::new("Mistral", "mistral")), GroupedItem::Separator, GroupedItem::Header("Cloud".to_string()), GroupedItem::Item(SelectorItem::new("AWS", "aws")), GroupedItem::Item(SelectorItem::new("Google Cloud", "gcp")), GroupedItem::Item(SelectorItem::new("Azure", "azure")), ]; let flow: Flow> = Flow::step( grouped_selector::grouped_selector(GroupedSelector::new(providers).max_height(7)), |provider: &'static str| { Flow::step( text_input::text_input( TextInput::new() .prompt("API Key: ") .placeholder("Paste your key…") .validator(validate_api_key) .display_width(42), ), move |key: String| { Flow::step( number_input::number_input( NumberInput::new(30.0) .prompt("Timeout (s): ") .min(1.0) .max(300.0) .integer(), ), move |timeout: f64| Flow::done(Some((provider, key, timeout))), ) }, ) }, ); match input::with_raw_mode(|mode| flow.run(mode)) { Ok(Ok(Some((provider, key, timeout)))) => { println!(); verify_api_key(provider, &key, timeout as u64); } Ok(Ok(None)) => {} Ok(Err(RunError::Cancelled)) => println!("{}", styled_warn("Cancelled")), Ok(Err(RunError::InputError(e))) => println!("Input error: {e}"), Err(e) => println!("Raw mode error: {e}"), } } fn verify_api_key(provider: &str, key: &str, _timeout_s: u64) { let width = terminal::get_size() .map(|(c, _)| c as usize) .unwrap_or(80) .min(60); let masked = if key.len() <= 8 { "••••••••".to_string() } else { format!("{}…{}", &key[..6], &key[key.len().saturating_sub(4)..]) }; println!(" Provider : {provider}"); println!(" Key : {masked}"); println!(" Timeout : {_timeout_s}s"); println!(); let mut checklist = Checklist::new(vec![ ChecklistItem::new("Validate key format"), ChecklistItem::new("Connect to endpoint"), ChecklistItem::new("Check permissions"), ChecklistItem::new("Cache credentials"), ]); let spin = spinner::Spinner::braille().label("Verifying…"); let mut view = View::create( &Block::vstack(vec![spin.frame(0), checklist.to_block()], 1), width, ); let steps: &[(usize, &str, u64)] = &[ (0, "valid", 200), (1, "connected", 500), (2, "authorized", 400), (3, "cached", 250), ]; let mut tick: usize = 0; for &(idx, note, ms) in steps { checklist.set_state(idx, CheckState::InProgress); for _ in 0..4 { thread::sleep(Duration::from_millis(ms / 4)); view.update(&Block::vstack( vec![spin.frame(tick), checklist.to_block()], 1, )); tick += 1; } checklist.set_state(idx, CheckState::Done); checklist.set_note(idx, Some(note.into())); view.update(&Block::vstack( vec![spin.frame(tick), checklist.to_block()], 1, )); tick += 1; } let ok = Block::text(vec![vec![Span::styled( format!("✓ {provider} configured successfully"), Style::new().bold().fg(Color::Green), )]]); view.update(&Block::vstack(vec![ok, checklist.to_block()], 1)); view.finish(); println!(); } fn styled_warn(msg: &str) -> String { ansi::styled(msg, &[ansi::fg(&Color::Yellow)]) }