-
-
Notifications
You must be signed in to change notification settings - Fork 1
F-029: feat(doctor): verify cloud provider connectivity #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,7 @@ use std::path::PathBuf; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use console::style; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use dialoguer::{Confirm, Editor, Input, Select}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use globset::{Glob, GlobSetBuilder}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use secrecy::ExposeSecret; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use tokio::signal; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use tokio::sync::mpsc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use tokio_util::sync::CancellationToken; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -579,11 +580,34 @@ impl App { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| other => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eprint!(" {} API key: ", other); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if self.config.api_key.is_some() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eprintln!("{}", style("configured").green()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eprintln!("{}", style("MISSING").red().bold()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eprint!(" {}: ", other); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| match self.config.api_key.as_ref() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| None => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eprintln!("{} (no API key configured)", style("MISSING").red().bold()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Some(key) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let redacted = redact_api_key(key.expose_secret()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Build the provider to reach verify(). Construction itself | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // can fail (e.g. HTTP client build); keep that non-fatal so | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // the rest of `doctor` still runs. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| match llm::create_provider(&self.config) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Err(e) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eprintln!("{}: {}", style("ERROR").red().bold(), e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(provider) => match provider.verify().await { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(()) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eprintln!( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "{} (key '{}')", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| style("reachable").green().bold(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| redacted | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Err(e) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eprintln!("{}: {}", style("ERROR").red().bold(), e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+584
to
+610
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| match self.config.api_key.as_ref() { | |
| None => { | |
| eprintln!("{} (no API key configured)", style("MISSING").red().bold()); | |
| } | |
| Some(key) => { | |
| let redacted = redact_api_key(key.expose_secret()); | |
| // Build the provider to reach verify(). Construction itself | |
| // can fail (e.g. HTTP client build); keep that non-fatal so | |
| // the rest of `doctor` still runs. | |
| match llm::create_provider(&self.config) { | |
| Err(e) => { | |
| eprintln!("{}: {}", style("ERROR").red().bold(), e); | |
| } | |
| Ok(provider) => match provider.verify().await { | |
| Ok(()) => { | |
| eprintln!( | |
| "{} (key '{}')", | |
| style("reachable").green().bold(), | |
| redacted | |
| ); | |
| } | |
| Err(e) => { | |
| eprintln!("{}: {}", style("ERROR").red().bold(), e); | |
| } | |
| }, | |
| } | |
| } | |
| let key = self | |
| .config | |
| .api_key | |
| .as_ref() | |
| .expect("non-Ollama providers are validated to have an API key before doctor runs"); | |
| let redacted = redact_api_key(key.expose_secret()); | |
| // Build the provider to reach verify(). Construction itself | |
| // can fail (e.g. HTTP client build); keep that non-fatal so | |
| // the rest of `doctor` still runs. | |
| match llm::create_provider(&self.config) { | |
| Err(e) => { | |
| eprintln!("{}: {}", style("ERROR").red().bold(), e); | |
| } | |
| Ok(provider) => match provider.verify().await { | |
| Ok(()) => { | |
| eprintln!( | |
| "{} (key '{}')", | |
| style("reachable").green().bold(), | |
| redacted | |
| ); | |
| } | |
| Err(e) => { | |
| eprintln!("{}: {}", style("ERROR").red().bold(), e); | |
| } | |
| }, |
Copilot
AI
Apr 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
redact_api_key walks the string twice (chars().count() and then chars().skip(...)), which is unnecessary and makes the function a bit harder to reason about. You can compute the last 4 characters in a single pass (e.g., via a reverse iterator) while still staying UTF-8 safe.
| let char_count = key.chars().count(); | |
| if char_count < MIN_LEN_FOR_SUFFIX { | |
| return "****".to_string(); | |
| } | |
| let tail: String = key.chars().skip(char_count - SUFFIX_LEN).collect(); | |
| let tail_chars: Vec<char> = key.chars().rev().take(SUFFIX_LEN).collect(); | |
| if tail_chars.len() < SUFFIX_LEN | |
| || key.chars().nth(MIN_LEN_FOR_SUFFIX - SUFFIX_LEN).is_none() | |
| { | |
| return "****".to_string(); | |
| } | |
| let tail: String = tail_chars.into_iter().rev().collect(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doctorprints a redacted API key suffix ((key '****1234')). Even partial disclosure of an API key can be sensitive (it can help correlate keys across logs/screenshots and increases exposure surface), and it also contradicts the doc comment’s goal of avoiding leaking secrets into terminal scrollback.Recommendation: don’t print any part of the key by default (just "configured"), or gate suffix/fingerprint display behind an explicit opt-in flag (e.g.,
--show-secrets) or an interactive confirmation.