From 4408a30adc27e7b06407775cabadf8d7817390f2 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Tue, 10 Mar 2026 14:41:08 -0400 Subject: [PATCH] Improve install flow ordering, doctor diagnostics, and callback page - Swap LLM providers and GitHub App steps so LLM setup comes first - List specific providers with connectivity issues in arc doctor warnings - Style the GitHub App callback page with a centered card and checkmark Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/crates/arc-cli/src/doctor.rs | 14 +++-- lib/crates/arc-cli/src/install.rs | 88 +++++++++++++++++++------------ 2 files changed, 63 insertions(+), 39 deletions(-) diff --git a/lib/crates/arc-cli/src/doctor.rs b/lib/crates/arc-cli/src/doctor.rs index 1902accde..1a6091b45 100644 --- a/lib/crates/arc-cli/src/doctor.rs +++ b/lib/crates/arc-cli/src/doctor.rs @@ -217,7 +217,7 @@ pub fn check_llm_providers( }) .collect(); - let mut has_live_error = false; + let mut failed_providers: Vec<&Provider> = Vec::new(); if let Some(results) = live_results { for (provider, result) in results { match result { @@ -225,7 +225,7 @@ pub fn check_llm_providers( text: format!("{provider} connectivity: OK"), }), Err(e) => { - has_live_error = true; + failed_providers.push(provider); details.push(CheckDetail { text: format!("{provider} connectivity: {e}"), }); @@ -242,13 +242,14 @@ pub fn check_llm_providers( details, remediation: Some("Set at least one provider API key".to_string()), } - } else if has_live_error { + } else if !failed_providers.is_empty() { + let names: Vec<_> = failed_providers.iter().map(|p| p.to_string()).collect(); CheckResult { name: "LLM providers".to_string(), status: CheckStatus::Warning, summary: format!("{count} of {total} configured (connectivity issues)"), details, - remediation: Some("Check provider API keys and network connectivity".to_string()), + remediation: Some(format!("Connectivity issues with: {}", names.join(", "))), } } else { CheckResult { @@ -1271,6 +1272,11 @@ mod tests { let result = check_llm_providers(&statuses, Some(&live)); assert_eq!(result.status, CheckStatus::Warning); assert!(result.details.iter().any(|d| d.text.contains("timeout"))); + let rem = result.remediation.unwrap(); + assert!( + rem.contains("anthropic"), + "remediation should name the failing provider: {rem}" + ); } // -- check_brave_search -- diff --git a/lib/crates/arc-cli/src/install.rs b/lib/crates/arc-cli/src/install.rs index 456df0564..1ba90e57f 100644 --- a/lib/crates/arc-cli/src/install.rs +++ b/lib/crates/arc-cli/src/install.rs @@ -378,9 +378,27 @@ async fn setup_github_app(arc_dir: &Path) -> Result> { if let Some(tx) = shutdown_tx.lock().unwrap().take() { let _ = tx.send(()); } - Html( - "

GitHub App created! You can close this tab.

".to_string(), - ) + Html(r#" + + + +Arc Setup + + + +
+
✓
+

GitHub App created

+

You can close this tab and return to your terminal.

+
+ +"#.to_string()) }), ); @@ -541,8 +559,38 @@ pub async fn run_install() -> Result<()> { eprintln!(); } - // Step 1: GitHub App setup - eprintln!("[Step 1/4] GitHub App"); + // Step 1: LLM providers + eprintln!("[Step 1/4] LLM providers"); + let provider_labels: Vec = Provider::ALL + .iter() + .map(|p| { + let env_vars = p.api_key_env_vars().join(" / "); + format!("{} ({})", provider_display_name(*p), env_vars) + }) + .collect(); + + let selected_indices: Vec = tokio::task::spawn_blocking({ + let labels = provider_labels.clone(); + move || prompt_multiselect("Which LLM providers do you want to configure?", &labels) + }) + .await??; + + let mut env_pairs: Vec<(String, String)> = Vec::new(); + for idx in selected_indices { + let provider = Provider::ALL[idx]; + let env_var = provider.api_key_env_vars()[0]; + let url = provider_key_url(provider); + eprintln!(" Get your API key at: {url}"); + + let prompt = env_var.to_string(); + let key: String = tokio::task::spawn_blocking(move || prompt_input(&prompt)).await??; + + env_pairs.push((env_var.to_string(), key)); + } + eprintln!(); + + // Step 2: GitHub App setup + eprintln!("[Step 2/4] GitHub App"); let mut github_env_pairs: Vec<(String, String)> = Vec::new(); { let setup_github = tokio::task::spawn_blocking(|| { @@ -611,36 +659,6 @@ pub async fn run_install() -> Result<()> { eprintln!(); } - // Step 2: LLM providers - eprintln!("[Step 2/4] LLM providers"); - let provider_labels: Vec = Provider::ALL - .iter() - .map(|p| { - let env_vars = p.api_key_env_vars().join(" / "); - format!("{} ({})", provider_display_name(*p), env_vars) - }) - .collect(); - - let selected_indices: Vec = tokio::task::spawn_blocking({ - let labels = provider_labels.clone(); - move || prompt_multiselect("Which LLM providers do you want to configure?", &labels) - }) - .await??; - - let mut env_pairs: Vec<(String, String)> = Vec::new(); - for idx in selected_indices { - let provider = Provider::ALL[idx]; - let env_var = provider.api_key_env_vars()[0]; - let url = provider_key_url(provider); - eprintln!(" Get your API key at: {url}"); - - let prompt = env_var.to_string(); - let key: String = tokio::task::spawn_blocking(move || prompt_input(&prompt)).await??; - - env_pairs.push((env_var.to_string(), key)); - } - eprintln!(); - // Step 3: Writing ~/.arc/.env eprintln!("[Step 3/4] Writing ~/.arc/.env"); let env_path = arc_dir.join(".env");