u/DataPhreak

Here's a python script that will write the git commits for you using a local model. Written for LMStudio but could easily be modified for vLLM, open router, or corpo models.

import os
import subprocess
import urllib.request
import urllib.error
import json
import sys

BASE_DIR = r"C:\ai"
LM_STUDIO_URL = "http://localhost:1234/v1"

def check_lm_studio():
    print("[*] Pinging LM Studio server...")
    try:
        # Hit the models endpoint to see what is loaded
        req = urllib.request.Request(f"{LM_STUDIO_URL}/models")
        with urllib.request.urlopen(req, timeout=3) as response:
            data = json.loads(response.read().decode('utf-8'))
            models = [m['id'] for m in data.get('data', [])]
            
            if not models:
                print("[!] Connected to LM Studio, but no model is currently loaded!")
                print("    Please load a model in LM Studio and try again.")
                return None
                
            print(f"[+] Connected! Active model: {models[0]}")
            if len(models) > 1:
                print(f"    (Found {len(models)} models, defaulting to {models[0]})")
            return models[0]
            
    except urllib.error.URLError as e:
        print("\n[!] Could not connect to LM Studio (WinError 10061).")
        print("    1. Open LM Studio.")
        print("    2. Go to the Local Server tab (↔️ icon).")
        print("    3. Click 'Start Server' on port 1234.")
        return None

def find_git_repos():
    repos = []
    try:
        for item in os.listdir(BASE_DIR):
            item_path = os.path.join(BASE_DIR, item)
            if os.path.isdir(item_path) and os.path.isdir(os.path.join(item_path, ".git")):
                repos.append(item_path)
    except Exception as e:
        print(f"[!] Error scanning directory: {e}")
    return repos

def select_repo(repos):
    if not repos:
        print(f"[!] No Git repositories found in {BASE_DIR}.")
        sys.exit(0)
        
    print("\n=== Select a Project ===")
    for i, repo in enumerate(repos):
        print(f"[{i + 1}] {os.path.basename(repo)}")
        
    while True:
        try:
            choice = input(f"\nSelect a project (1-{len(repos)}) or 'q' to quit: ").strip()
            if choice.lower() == 'q':
                sys.exit(0)
            
            index = int(choice) - 1
            if 0 <= index < len(repos):
                return repos[index]
        except ValueError:
            pass
        print("[!] Invalid selection. Please enter a valid number.")

def get_staged_diff(repo_path):
    repo_name = os.path.basename(repo_path)
    print(f"\n[*] Extracting staged changes from '{repo_name}'...")
    result = subprocess.run(['git', '-C', repo_path, 'diff', '--cached'], capture_output=True, text=True)
    return result.stdout.strip()

def generate_commit_message(diff, model_id):
    print("[*] Generating commit summary and description...")
    url = f"{LM_STUDIO_URL}/chat/completions"
    headers = {"Content-Type": "application/json"}
    
    system_prompt = (
        "You are an expert software engineer. Write a conventional commit message based on the git diff. "
        "You must include a short summary line, followed by a blank line, followed by a description body.\n\n"
        "Rules:\n"
        "- Stylistic preference: 'say more with less'. Keep the description concise, punchy, and feature-highlighted.\n"
        "- Format:\n<type>(<scope>): <summary>\n\n- <description point 1>\n- <description point 2>\n"
        "- Do not include markdown code blocks, introductory text, or quotes. Output only the raw commit text."
    )

    data = {
        "model": model_id,
        "messages": [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": diff}
        ],
        "temperature": 0.3,
        "max_tokens": 200
    }

    try:
        req = urllib.request.Request(url, data=json.dumps(data).encode('utf-8'), headers=headers)
        with urllib.request.urlopen(req) as response:
            result = json.loads(response.read().decode('utf-8'))
            return result['choices'][0]['message']['content'].strip()
    except Exception as e:
        print(f"\n[!] Generation failed: {e}")
        sys.exit(1)

def main():
    print("========================================")
    print("      AI Commit Message Generator       ")
    print("========================================\n")

    # Step 1: Ensure LM Studio is awake and get the active model
    active_model = check_lm_studio()
    if not active_model:
        input("\nPress Enter to exit...")
        sys.exit(1)

    # Step 2: Select Project
    repos = find_git_repos()
    selected_repo = select_repo(repos)
    
    # Step 3: Get Diff
    diff = get_staged_diff(selected_repo)
    if not diff:
        print(f"\n[!] No staged changes found in {os.path.basename(selected_repo)}.")
        print("[!] Stage your files in GitKraken first, then run this tool again.")
        input("\nPress Enter to exit...")
        sys.exit(0)
        
    print(f"[*] Found {len(diff.splitlines())} lines of staged changes.")
    
    # Step 4: Generate
    commit_msg = generate_commit_message(diff, active_model)
    
    print("\n========================================")
    print(commit_msg)
    print("========================================\n")
    
    # Step 5: Copy to Clipboard
    try:
        subprocess.run('clip', input=commit_msg, text=True, check=True, shell=True)
        print("[Success] Commit message copied to your clipboard!")
    except Exception as e:
        print(f"[Note] Could not auto-copy to clipboard: {e}")

    input("\nPress Enter to close...")

if __name__ == "__main__":
    main()
reddit.com
u/DataPhreak — 29 days ago