Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ It can summarize diffs, ask you how each file relates to the purpose of the comm

- **Interactive “ask” mode** – Classify each file as main, supporting, or consequential.
- **Quick mode** – Instantly summarize staged diffs into a commit message.
- **LLM-powered** – Uses OpenAI’s GPT models to generate concise and structured messages.
- **LLM-powered** – Uses OpenAI, or a local model via [Ollama](#providers) or [LM Studio](#providers).
- **Configurable** – Choose models, tweak behavior, and set defaults in a config file.
- **Pull request summaries** – Generate clean, readable PR descriptions from your commit history.

---

## Installation

You’ll need an OpenAI API key set as an environment variable:
To use OpenAI (the default provider) you’ll need an API key set as an environment variable:

```bash
export OPENAI_API_KEY="sk-..."
Expand Down Expand Up @@ -126,6 +126,25 @@ model = "gpt-5-nano"

---

## Providers

Set `provider` to choose a backend. Any of them can be overridden per repository.

| Provider | Default `url` | API key |
|------------|--------------------------|----------|
| `openai` | `https://api.openai.com` | required |
| `ollama` | `http://localhost:11434` | not used |
| `lmstudio` | `http://localhost:1234` | optional |

```toml
[default]
provider = "lmstudio"
model = "qwen/qwen3-coder-30b"
url = "http://192.168.1.16:1234/v1"
```

---

## Roadmap

- [x] Support for local/offline LLMs (Ollama, LM Studio).
Expand Down
11 changes: 9 additions & 2 deletions commitbot.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,28 @@ model = "gpt-5-nano"
# Optional: OpenAI-style API key (falls back to env OPENAI_API_KEY)
openai_api_key = "your api key here"

# Optional: provider base URL (e.g. http://localhost:11434 for Ollama)
# Optional: provider base URL (e.g. http://localhost:11434 for local LLM)
url = "https://api.openai.com"

# 1 = fully serial, >1 = parallel API calls
max_concurrent_requests = 4


["mikegarde/commitbot"]
["company/repo"]
provider = "openai"
stream = false
model = "gpt-4o-mini"
openai_api_key = "alternative for spend identification"
max_concurrent_requests = 8


["MikeGarde/commitbot"]
provider = "lmstudio"
model = "google/gemma-4-12b-qat"
url = "http://GPU.localdomain:1234/v1"
max_concurrent_requests = 2


["company/enterprise"]
# Enterprise / self-hosted style config
base_url = "https://enterprise.api.endpoint"
Expand Down
2 changes: 1 addition & 1 deletion src/cli_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub struct Cli {
#[arg(short = 'k', long, global = true)]
pub api_key: Option<String>,

/// LLM provider / API style (openai or ollama)
/// LLM provider / API style (openai, ollama, or lmstudio)
#[arg(long, global = true)]
pub provider: Option<String>,

Expand Down
9 changes: 6 additions & 3 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::path::{Path, PathBuf};
/// Final resolved configuration for commitbot.
#[derive(Debug, Clone)]
pub struct Config {
/// LLM provider (openai, ollama)
/// LLM provider (openai, ollama, lmstudio)
pub provider: String,
/// OpenAI API key for authentication (sensitive – redacted in logs)
pub openai_api_key: Option<String>,
Expand Down Expand Up @@ -55,13 +55,16 @@ impl Config {

let max_concurrent_requests = r.get_usize("max_concurrent_requests", 4);
let stream = r.get_bool("stream", true);
let request_timeout_secs = r.get_u64("request_timeout_secs", 90);
let request_timeout_secs = r.get_u64("request_timeout_secs", 300);

// Cleanup: trim stray quotes if any upstream included them
let provider = provider.trim_matches('"').to_string();
let model = model.trim_matches('"').to_string();
let openai_api_key = openai_api_key.map(|s| s.trim_matches('"').to_string());
let base_url = base_url.map(|s| s.trim_matches('"').to_string());
// A blank url is the same as no url
let base_url = base_url
.map(|s| s.trim_matches('"').trim().to_string())
.filter(|s| !s.is_empty());

if provider == "openai" && openai_api_key.is_none() {
return Err(anyhow!(
Expand Down
Loading
Loading