AI Commit Generator: A Native Windows Desktop App That Writes Your Git Commits With AI
You know that moment you've spent two hours debugging, the fix is finally in, and now you have to write a commit message. Ugh.
I built AI Commit Generator to solve exactly that. It's a native Windows desktop app that:
- Grabs your
git diff(staged or unstaged) - Sends it to an AI model (via OpenRouter or Google AI Studio)
- Returns a clean, conventional commit message
- Lets you copy it, edit it, regenerate it, or just commit and push right there
All in a 550×600 pixel window that lives in your system tray.
Here's a breakdown of the architecture and the interesting technical decisions along the way.
The Tech Stack: C++ + WebView2
The app is written in C++17 using Win32 APIs and Microsoft Edge WebView2 for the UI. Yes, really C++ and web technologies coexisting in the same binary.
The core idea:
-
C++ handles the heavy lifting: spawning
gitprocesses, managing the system tray, reading/writing config, HTTP requests to GitHub for update checks. - WebView2 handles the UI: the entire interface is HTML/CSS/JS rendered inside an embedded Edge WebView2 control.
This isn't a framework — it's raw Win32 CreateWindowExW, a message loop, and ICoreWebView2Controller. No Electron, no Tauri, no extra runtime dependencies beyond the Edge WebView2 runtime that ships with Windows 10/11.
Why WebView2 and not a full web framework?
I wanted a small, native-feeling Windows app. Electron bundles Chromium (~150MB+). WebView2 uses the Edge runtime already installed on the user's machine. The result is a tiny installer (~2MB compressed).
The Bridge Pattern: C++ ↔ JavaScript IPC
The most interesting architectural piece is the Bridge a C++ class that handles bidirectional communication between the WebView2 frontend and the native backend.
Here's how it works:
JS → C++ (frontend calls native code)
The JavaScript frontend calls window.chrome.webview.postMessage() with a JSON-RPC-style message:
{
"id": "1",
"method": "getGitDiff",
"params": { "repoPath": "C:\\projects\\my-app", "staged": false }
}
The C++ Bridge class receives this via ICoreWebView2WebMessageReceivedEventHandler, parses the method name, dispatches to the appropriate handler, and sends a response back:
{
"id": "1",
"result": "\"diff --git a/file.js b/file.js\\n...\"",
"error": null
}
Available RPC Methods
The bridge exposes these native operations to the frontend:
| Method | What it does |
|---|---|
getConfig |
Read settings from %APPDATA%\AiCommitGen\settings.json
|
setConfig |
Write a config key-value pair |
selectDirectory |
Open a native Win32 folder picker dialog |
getGitDiff |
Run git diff or git diff --cached
|
getGitLog |
Run git log --oneline -10
|
commitAndPush |
Run git add -A, git commit -m "...", then git push with retry |
checkForUpdates |
Hit the GitHub Releases API for the latest version |
downloadUpdate |
Download the installer exe and launch it |
This RPC pattern means the JavaScript never directly executes shell commands — everything goes through the bridge, which validates inputs and handles errors cleanly.
The AI Integration: Two Providers, One Prompt
The app supports two AI providers:
OpenRouter
Uses the standard OpenAI-compatible chat completions API. Supports models like GPT-4o, Claude 3.5 Haiku, Gemini Flash, and Llama 3.3.
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + apiKey,
'Content-Type': 'application/json',
'HTTP-Referer': 'https://github.com/ai-commit-generator',
},
body: JSON.stringify({
model: model,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: 'Recent commits:\n...\n\nGit diff:\n...' },
],
temperature: 0.3,
max_tokens: 300,
}),
});
Google AI Studio (Gemini)
Uses the Gemini REST API directly, with special handling for thinking models:
const config = { temperature: 0.3, maxOutputTokens: 1024 };
if (model.indexOf('gemini-2.5-flash') === 0) {
config.thinkingConfig = { thinkingBudget: 0 };
} else if (model.indexOf('gemini-3') === 0) {
config.thinkingConfig = { thinkingLevel: 'MINIMAL' };
}
I learned the hard way that Gemini 2.5 Flash with thinking enabled will eat your entire token budget on chain-of-thought before producing the actual commit message. Disabling thinking (thinkingBudget: 0) for commit messages is a must.
The System Prompt
The prompt that actually generates the commit message follows conventional commits format:
You are an expert at writing clear, concise git commit messages.
Given a git diff and recent commit history, generate a fitting commit message.Rules:
- Use conventional commits format: type(scope): description
- Types: feat, fix, chore, docs, style, refactor, perf, test, ci, build, revert
- Keep the subject line under 72 characters
- Focus on WHAT and WHY, not HOW
- Match the style of recent commits shown in the log
The key insight: sending the last 10 commits as context helps the AI match the existing commit style of the project.
Smart Git Operations
The commitAndPush handler does more than just run commands — it has real-world resilience built in:
// Push with retry for transient server errors
const int maxRetries = 3;
for (int i = 0; i < maxRetries; i++) {
auto pushRes = RunCmd(L"git push", repoPath);
if (pushRes.exitCode == 0) break;
// Retry on 500/502/503 errors
if (pushRes.output.find(L"500") != std::wstring::npos ||
pushRes.output.find(L"502") != std::wstring::npos ||
pushRes.output.find(L"503") != std::wstring::npos) {
Sleep(1000 * (1 << i)); // Exponential backoff
continue;
}
break;
}
It also detects "nothing to commit" gracefully, handles "Everything up-to-date", and distinguishes between commit failures and push failures so you know exactly what went wrong.
The Window Plumbing
Setting up a Win32 window with WebView2 is surprisingly involved. Here's the rough lifecycle:
int WINAPI WinMain(HINSTANCE hInstance, ...) {
// 1. Register window class with dark background
wc.hbrBackground = CreateSolidBrush(RGB(13, 17, 23));
RegisterClassExW(&wc);
// 2. Create fixed-size window (no resize)
g_hWnd = CreateWindowExW(0, L"AiCommitGen", ...,
WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX & ~WS_THICKFRAME, ...);
// 3. Initialize system tray icon
InitTrayIcon();
// 4. Create WebView2 with custom user data directory
CreateCoreWebView2EnvironmentWithOptions(
nullptr, userDataDir.c_str(), envOptions.Get(), ...);
// 5. Use virtual host mapping to serve local files
webview3->SetVirtualHostNameToFolderMapping(
L"app.commitgen", (exeDir + L"\\web").c_str(),
COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_DENY_CORS);
g_webview->Navigate(L"https://app.commitgen/index.html");
// 6. Message loop
MSG msg;
while (GetMessageW(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
One subtle detail: virtual host mapping (SetVirtualHostNameToFolderMapping) is used instead of navigating to file:// directly. This avoids the file:// origin restrictions in WebView2 and gives the frontend a proper https:// origin for CORS and security features.
System Tray: The Unsung UX
The close button doesn't actually close the app — it hides to the system tray. This is intentional: commit messages are most useful when the app is always one double-click away.
case WM_CLOSE:
if (!g_isQuitting) {
g_webviewController->put_IsVisible(FALSE);
ShowWindow(hWnd, SW_HIDE);
return 0; // Don't destroy — just hide
}
break;
The tray icon supports:
- Double-click to restore
- Right-click menu with Open and Exit options
- "Start hidden in tray" option for power users who want it always available but never visible
Auto-Update System
The app checks GitHub Releases on startup. The update flow:
- C++ sends a WinHTTP request to
github.com/NSTechBytes/ai-commit-generator/releases/latest - Parses the redirect to extract the tag name
- Compares versions in JavaScript using a semver comparison
- If a newer version exists, downloads the installer exe and launches it
The version is embedded in the Windows executable's VS_FIXEDFILEINFO resource and read at runtime:
static std::wstring GetApplicationVersion() {
// ... reads VS_FIXEDFILEINFO from the running .exe
return L"1.1.0";
}
Project Structure
AiCommitGen.sln # Visual Studio solution
AiCommitGen/
main.cpp # Win32 entry point + WebView2 init
bridge.h / bridge.cpp # C++ ↔ JS RPC bridge
web/
index.html # UI (single-page, all CSS inline)
index.js # Frontend logic (~400 lines)
assets/ # Icons, images
installer/
setup.nsi # NSIS installer script
setup.ps1 # Release build + installer pipeline
Build.ps1 # Debug/Release build script
Getting Started
Prerequisites:
- Windows 10/11
- Visual Studio 2022 (Desktop development with C++)
- WebView2 Runtime (comes with Windows)
- NSIS (for building the installer)
Build:
# Debug build
./Build.ps1
# Release + Installer
./setup.ps1
Configuration: API keys and settings are stored in %APPDATA%\AiCommitGen\settings.json.
What I Learned
WebView2 is surprisingly capable. It's basically a modern IE/Edge control that renders Chromium content. The virtual host mapping feature alone makes it viable for local-file apps.
JSON parsing in C++ without libraries is tedious. I wrote a custom
JsonGetString/ParseFlatStringObjectimplementation. It works for flat config files, but I wouldn't recommend it for nested JSON.Git commands through
CreateProcessWneed careful pipe management. Redirecting stdout/stderr through Win32 pipes, then reading from the read end until EOF, is a pattern that looks simple but has subtle gotchas around handle inheritance and buffer sizes.Conventional commits + recent commit history = better AI output. Sending the last 10 commits as context makes the AI match the project's actual style, not just a generic format.
Try It Out
The app is open source on GitHub: NSTechBytes/ai-commit-generator
Install the latest release, paste your OpenRouter or Google AI Studio API key, select a repo, and hit Generate. It's the fastest way I've found to go from "I have no idea what to call this commit" to a clean push.
Built with C++17, Win32, WebView2, and a healthy impatience for writing commit messages.










