Skip to content

Commit 001bfcb

Browse files
committed
feat: add doctor diagnostics and shell completions commands
Doctor checks Ollama connectivity, model availability, and git repo status. Completions generates shell-specific completion scripts via clap_complete. Config output now includes all fields.
1 parent 8e52700 commit 001bfcb

3 files changed

Lines changed: 104 additions & 6 deletions

File tree

src/app.rs

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use tokio::signal;
99
use tokio::sync::mpsc;
1010
use tokio_util::sync::CancellationToken;
1111

12-
use crate::cli::Cli;
12+
use crate::cli::{Cli, Commands};
1313
use crate::config::Config;
1414
use crate::error::{Error, Result};
1515
use crate::services::{
@@ -44,7 +44,7 @@ impl App {
4444

4545
// Handle subcommands
4646
if let Some(ref cmd) = self.cli.command {
47-
return self.handle_command(cmd);
47+
return self.handle_command(cmd).await;
4848
}
4949

5050
self.generate_commit().await
@@ -216,19 +216,23 @@ impl App {
216216
Ok(())
217217
}
218218

219-
fn handle_command(&self, cmd: &crate::cli::Commands) -> Result<()> {
219+
async fn handle_command(&self, cmd: &Commands) -> Result<()> {
220220
match cmd {
221-
crate::cli::Commands::Init => {
221+
Commands::Init => {
222222
let path = Config::create_default()?;
223223
println!("Created config: {}", path.display());
224224
Ok(())
225225
}
226-
crate::cli::Commands::Config => {
226+
Commands::Config => {
227227
println!("Provider: {}", self.config.provider);
228228
println!("Model: {}", self.config.model);
229229
println!("Ollama host: {}", self.config.ollama_host);
230230
println!("Max diff lines: {}", self.config.max_diff_lines);
231231
println!("Max file lines: {}", self.config.max_file_lines);
232+
println!("Max context chars: {}", self.config.max_context_chars);
233+
println!("Timeout: {}s", self.config.timeout_secs);
234+
println!("Temperature: {}", self.config.temperature);
235+
println!("Max tokens: {}", self.config.num_predict);
232236
println!();
233237
println!("[format]");
234238
println!(" include_body: {}", self.config.format.include_body);
@@ -239,7 +243,90 @@ impl App {
239243
);
240244
Ok(())
241245
}
246+
Commands::Doctor => self.run_doctor().await,
247+
Commands::Completions { shell } => {
248+
let mut cmd = <Cli as clap::CommandFactory>::command();
249+
clap_complete::generate(*shell, &mut cmd, "commitbee", &mut std::io::stdout());
250+
Ok(())
251+
}
252+
}
253+
}
254+
255+
async fn run_doctor(&self) -> Result<()> {
256+
eprintln!("{} Running diagnostics...\n", style("→").cyan());
257+
258+
// Config summary
259+
eprintln!("{}", style("Configuration").bold().underlined());
260+
eprintln!(" Provider: {}", self.config.provider);
261+
eprintln!(" Model: {}", self.config.model);
262+
eprintln!(" Timeout: {}s", self.config.timeout_secs);
263+
if let Some(ref path) = Config::config_path() {
264+
let status = if path.exists() { "found" } else { "not found" };
265+
eprintln!(" Config file: {} ({})", path.display(), status);
266+
}
267+
eprintln!();
268+
269+
// Provider connectivity
270+
eprintln!("{}", style("Provider Check").bold().underlined());
271+
match self.config.provider {
272+
crate::config::Provider::Ollama => {
273+
eprint!(" Ollama ({}): ", self.config.ollama_host);
274+
let provider = llm::create_provider(&self.config)?;
275+
match provider.verify().await {
276+
Ok(()) => {
277+
eprintln!("{}", style("OK").green().bold());
278+
eprintln!(
279+
" Model '{}': {}",
280+
self.config.model,
281+
style("available").green()
282+
);
283+
}
284+
Err(Error::OllamaNotRunning { .. }) => {
285+
eprintln!("{}", style("NOT RUNNING").red().bold());
286+
eprintln!(" Start with: {}", style("ollama serve").yellow());
287+
}
288+
Err(Error::ModelNotFound { ref available, .. }) => {
289+
eprintln!("{}", style("connected").green());
290+
eprintln!(
291+
" Model '{}': {}",
292+
self.config.model,
293+
style("NOT FOUND").red().bold()
294+
);
295+
eprintln!(
296+
" Pull with: {}",
297+
style(format!("ollama pull {}", self.config.model)).yellow()
298+
);
299+
if !available.is_empty() {
300+
eprintln!(" Available: {}", available.join(", "));
301+
}
302+
}
303+
Err(e) => {
304+
eprintln!("{}: {}", style("ERROR").red().bold(), e);
305+
}
306+
}
307+
}
308+
other => {
309+
eprint!(" {} API key: ", other);
310+
if self.config.api_key.is_some() {
311+
eprintln!("{}", style("configured").green());
312+
} else {
313+
eprintln!("{}", style("MISSING").red().bold());
314+
}
315+
}
242316
}
317+
eprintln!();
318+
319+
// Git check
320+
eprintln!("{}", style("Git Repository").bold().underlined());
321+
match GitService::discover() {
322+
Ok(_) => eprintln!(" Repository: {}", style("found").green()),
323+
Err(_) => eprintln!(" Repository: {}", style("NOT FOUND").red().bold()),
324+
}
325+
326+
eprintln!();
327+
eprintln!("{} Diagnostics complete.", style("✓").green().bold());
328+
329+
Ok(())
243330
}
244331

245332
fn print_status(&self, msg: &str) {

src/cli.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,12 @@ pub enum Commands {
4747
Init,
4848
/// Show current configuration
4949
Config,
50+
/// Check configuration and provider connectivity
51+
Doctor,
52+
/// Generate shell completions
53+
Completions {
54+
/// Shell to generate completions for
55+
#[arg(value_enum)]
56+
shell: clap_complete::Shell,
57+
},
5058
}

src/error.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ use thiserror::Error;
1111
#[derive(Error, Diagnostic, Debug)]
1212
pub enum Error {
1313
#[error("No staged changes found")]
14-
#[diagnostic(code(commitbee::git::no_staged), help("Stage files with: git add <files>"))]
14+
#[diagnostic(
15+
code(commitbee::git::no_staged),
16+
help("Stage files with: git add <files>")
17+
)]
1518
NoStagedChanges,
1619

1720
#[error("Not a git repository")]

0 commit comments

Comments
 (0)