diff --git a/CONFIGURATION.md b/CONFIGURATION.md new file mode 100644 index 0000000..4d9cec2 --- /dev/null +++ b/CONFIGURATION.md @@ -0,0 +1,220 @@ +# Configuration Guide + +This guide covers all configuration options for zsh-git-ai. + +## Table of Contents +- [Provider Selection](#provider-selection) +- [API Keys](#api-keys) +- [Commit Message Customization](#commit-message-customization) +- [Provider-Specific Settings](#provider-specific-settings) +- [Advanced Configuration](#advanced-configuration) + +## Provider Selection + +Choose your AI provider by setting the `ZSH_GIT_AI_PROVIDER` environment variable: + +```bash +export ZSH_GIT_AI_PROVIDER="anthropic" # Default +``` + +Available providers: +- `anthropic` - Claude (default) +- `openai` - GPT +- `gemini` - Google Gemini +- `ollama` - Local models + +## API Keys + +Each provider requires an API key (except Ollama for local models): + +```bash +# Anthropic Claude +export ANTHROPIC_API_KEY="your-api-key-here" + +# OpenAI GPT +export OPENAI_API_KEY="your-api-key-here" + +# Google Gemini +export GEMINI_API_KEY="your-api-key-here" +``` + +> 💡 **Tip**: Add these to your `~/.zshrc` to make them permanent + +## Commit Message Customization + +### Message Style + +Control the format of generated commit messages: + +```bash +export ZSH_GIT_AI_STYLE="simple" # Options: simple (default), conventional, semantic +``` + +#### Style Examples: + +**Simple** (default): +``` +Add user authentication feature +``` + +**Conventional** (follows [Conventional Commits](https://www.conventionalcommits.org/)): +``` +feat(auth): add user authentication +``` + +**Semantic**: +``` +Add user authentication with JWT support +``` + +### Message Length + +Customize the maximum length of commit messages: + +```bash +export ZSH_GIT_AI_MAX_LENGTH="100" # Default: 72 +``` + +### Custom Prompt + +For complete control, provide your own prompt template: + +```bash +export ZSH_GIT_AI_PROMPT="Generate a commit message that explains the why, not just the what:" +``` + +Creative examples: +```bash +# Haiku style +export ZSH_GIT_AI_PROMPT="Write a commit message as a haiku:" + +# Explain like I'm 5 +export ZSH_GIT_AI_PROMPT="Write a commit message that a 5-year-old could understand:" + +# Include ticket numbers +export ZSH_GIT_AI_PROMPT="Generate a commit message and always prefix with [JIRA-XXX] if you see a ticket number in the branch name:" +``` + +## Provider-Specific Settings + +### Ollama Configuration + +For local Ollama models: + +```bash +export OLLAMA_MODEL="llama2" # Default: llama2 +export OLLAMA_API_URL="http://localhost:11434" # Default: http://localhost:11434 +``` + +Popular Ollama models: +- `llama2` - Default, balanced +- `codellama` - Optimized for code +- `mistral` - Fast and efficient +- `mixtral` - High quality + +## Advanced Configuration + +### Environment Persistence + +Make your configuration permanent by adding to your shell config: + +```bash +# Add to ~/.zshrc +echo 'export ZSH_GIT_AI_PROVIDER="openai"' >> ~/.zshrc +echo 'export OPENAI_API_KEY="your-key"' >> ~/.zshrc +echo 'export ZSH_GIT_AI_STYLE="conventional"' >> ~/.zshrc +echo 'export ZSH_GIT_AI_MAX_LENGTH="100"' >> ~/.zshrc +``` + +### Configuration Precedence + +Settings are applied in this order (later overrides earlier): +1. Default values +2. Environment variables +3. Custom prompt (overrides style settings) + +### Per-Project Configuration + +You can set different configurations per project using direnv: + +1. Install direnv: `brew install direnv` +2. Create `.envrc` in your project: +```bash +export ZSH_GIT_AI_STYLE="conventional" +export ZSH_GIT_AI_MAX_LENGTH="50" +``` +3. Allow direnv: `direnv allow` + +### SSH and Remote Sessions + +To use zsh-git-ai over SSH: + +1. Configure SSH to forward environment variables: +```bash +# ~/.ssh/config +Host myserver + SendEnv ANTHROPIC_API_KEY ZSH_GIT_AI_* +``` + +2. Configure the server to accept them: +```bash +# /etc/ssh/sshd_config (on server) +AcceptEnv ANTHROPIC_API_KEY ZSH_GIT_AI_* +``` + +### tmux Configuration + +For tmux users, ensure environment variables are updated: + +```bash +# ~/.tmux.conf +set-option -g update-environment "ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY ZSH_GIT_AI_*" +``` + +## Examples + +### Example 1: Conventional Commits for a TypeScript Project + +```bash +export ZSH_GIT_AI_PROVIDER="anthropic" +export ZSH_GIT_AI_STYLE="conventional" +export ZSH_GIT_AI_MAX_LENGTH="72" +``` + +### Example 2: Detailed Commits for Documentation + +```bash +export ZSH_GIT_AI_STYLE="semantic" +export ZSH_GIT_AI_MAX_LENGTH="100" +export ZSH_GIT_AI_PROMPT="Write a detailed commit message focusing on what changed and why it matters to users:" +``` + +### Example 3: Quick Local Development with Ollama + +```bash +export ZSH_GIT_AI_PROVIDER="ollama" +export OLLAMA_MODEL="codellama" +export ZSH_GIT_AI_STYLE="simple" +``` + +## Troubleshooting + +### Configuration Not Working? + +1. Verify environment variables are set: +```bash +echo $ZSH_GIT_AI_PROVIDER +echo $ZSH_GIT_AI_STYLE +``` + +2. Reload your shell configuration: +```bash +source ~/.zshrc +``` + +3. Check for typos in variable names (they're case-sensitive) + +### Need Help? + +- Check the [Troubleshooting Guide](TROUBLESHOOTING.md) +- Open an issue on [GitHub](https://github.com/matheusml/zsh-git-ai/issues) \ No newline at end of file diff --git a/INSTALL.md b/INSTALL.md index fc35a74..243f717 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -133,44 +133,27 @@ echo "source ~/.zsh-git-ai.zsh" >> ~/.zshrc source ~/.zshrc ``` -## Provider Setup +## Configuration -### 🔧 Supported Providers +zsh-git-ai supports multiple AI providers and extensive customization options. -zsh-git-ai supports multiple AI providers. Set your preferred provider with: +### Quick Setup ```bash -export ZSH_GIT_AI_PROVIDER="anthropic" # Default -``` - -#### Available Providers: - -**Anthropic Claude** (default) -```bash +# Set your provider (default: anthropic) export ZSH_GIT_AI_PROVIDER="anthropic" -export ANTHROPIC_API_KEY="your-api-key-here" -``` -**OpenAI GPT** -```bash -export ZSH_GIT_AI_PROVIDER="openai" -export OPENAI_API_KEY="your-api-key-here" -``` - -**Google Gemini** -```bash -export ZSH_GIT_AI_PROVIDER="gemini" -export GEMINI_API_KEY="your-api-key-here" -``` - -**Ollama** (local models) -```bash -export ZSH_GIT_AI_PROVIDER="ollama" -export OLLAMA_MODEL="llama2" # Optional, defaults to llama2 -export OLLAMA_API_URL="http://localhost:11434" # Optional +# Set your API key +export ANTHROPIC_API_KEY="your-api-key-here" ``` -> 💡 **Tip**: Add these exports to your `~/.zshrc` to make them permanent +> 📚 **For detailed configuration options**, including: +> - All supported providers and their settings +> - Commit message customization (styles, length, custom prompts) +> - Provider-specific configuration +> - Advanced setup (per-project config, SSH, tmux) +> +> **See the [Configuration Guide](CONFIGURATION.md)** ## API Key Setup @@ -397,7 +380,7 @@ Or set up an alias: echo 'alias update-zsh-git-ai="cd ~/.zsh-git-ai && git pull && cd - && source ~/.zshrc"' >> ~/.zshrc ``` -## Advanced Configuration +## Advanced Setup ### Custom Installation Location @@ -409,30 +392,13 @@ git clone https://github.com/matheusml/zsh-git-ai /custom/path/zsh-git-ai echo "source /custom/path/zsh-git-ai/zsh-git-ai.zsh" >> ~/.zshrc ``` -### Using with Tmux - -Add to `.tmux.conf` to preserve environment: -```bash -# Add all possible API keys -set-option -g update-environment "ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY ZSH_GIT_AI_PROVIDER" -``` - -### Using with SSH - -Forward your API key when SSHing: -```bash -# Send API key based on your provider -ssh -o SendEnv=ANTHROPIC_API_KEY user@host -# Or for other providers: -# ssh -o SendEnv=OPENAI_API_KEY user@host -# ssh -o SendEnv=GEMINI_API_KEY user@host -# ssh -o "SendEnv=ZSH_GIT_AI_PROVIDER ANTHROPIC_API_KEY" user@host -``` - -Add to server's `/etc/ssh/sshd_config`: -``` -AcceptEnv ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY ZSH_GIT_AI_PROVIDER -``` +> 📚 **For more advanced configuration options**, including: +> - tmux integration +> - SSH environment forwarding +> - Per-project configuration +> - Custom commit message styles and prompts +> +> **See the [Configuration Guide](CONFIGURATION.md)** --- diff --git a/README.md b/README.md index 4a3da31..66314b5 100644 --- a/README.md +++ b/README.md @@ -99,4 +99,5 @@ The AI examines your staged changes and understands: ## Documentation - 📦 **[Installation & Setup](INSTALL.md)** - Detailed installation instructions for all package managers +- ⚙️ **[Configuration Guide](CONFIGURATION.md)** - Customize providers, commit styles, and advanced settings - 🤝 **[Contributing](CONTRIBUTING.md)** - Help make zsh-git-ai better! diff --git a/lib/prompt_builder.zsh b/lib/prompt_builder.zsh new file mode 100644 index 0000000..98294d1 --- /dev/null +++ b/lib/prompt_builder.zsh @@ -0,0 +1,82 @@ +#!/usr/bin/env zsh + +# Function to build the commit message prompt based on configuration +build_commit_prompt() { + local diff="$1" + local git_status="$2" + local max_length="${ZSH_GIT_AI_MAX_LENGTH:-72}" + local style="${ZSH_GIT_AI_STYLE:-simple}" + + # If custom prompt is provided, use it directly + if [[ -n "$ZSH_GIT_AI_PROMPT" ]]; then + echo "$ZSH_GIT_AI_PROMPT" + echo "" + echo "Git status:" + echo "$git_status" + echo "" + echo "Git diff:" + echo "$diff" + return + fi + + # Otherwise, build prompt based on style + case "$style" in + conventional) + cat <(): +- Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert +- Be no longer than $max_length characters for the subject line +- Use lowercase for type and scope +- No period at the end +- Only output the commit message, nothing else + +Git status: +$git_status + +Git diff: +$diff +EOF + ;; + + semantic) + cat < /dev/null; then diff --git a/lib/providers/gemini.zsh b/lib/providers/gemini.zsh index 2dda8ba..967aec8 100644 --- a/lib/providers/gemini.zsh +++ b/lib/providers/gemini.zsh @@ -2,6 +2,9 @@ # Gemini provider for git-commit-ai +# Source the prompt builder +source "${0:A:h}/../prompt_builder.zsh" + gemini_api_url="https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent" gemini_model="gemini-pro" @@ -17,20 +20,7 @@ gemini_generate_commit_message() { local git_diff="$1" local status_output="$2" - local prompt="You are a helpful assistant that writes concise and descriptive git commit messages. - -Based on the following git diff and status, generate a clear and concise commit message. -The commit message should: -- Be written in the imperative mood (e.g., 'Add feature' not 'Added feature') -- Be no longer than 72 characters -- Clearly describe what the change does, not how it does it -- Only output the commit message, nothing else - -Git status: -$status_output - -Git diff: -$git_diff" + local prompt=$(build_commit_prompt "$git_diff" "$status_output") local json_payload if command -v jq &> /dev/null; then diff --git a/lib/providers/ollama.zsh b/lib/providers/ollama.zsh index de47b5a..c81eaeb 100644 --- a/lib/providers/ollama.zsh +++ b/lib/providers/ollama.zsh @@ -2,6 +2,9 @@ # Ollama provider for git-commit-ai +# Source the prompt builder +source "${0:A:h}/../prompt_builder.zsh" + ollama_api_url="${OLLAMA_API_URL:-http://localhost:11434}" ollama_model="${OLLAMA_MODEL:-llama2}" @@ -25,20 +28,7 @@ ollama_generate_commit_message() { local git_diff="$1" local status_output="$2" - local prompt="You are a helpful assistant that writes concise and descriptive git commit messages. - -Based on the following git diff and status, generate a clear and concise commit message. -The commit message should: -- Be written in the imperative mood (e.g., 'Add feature' not 'Added feature') -- Be no longer than 72 characters -- Clearly describe what the change does, not how it does it -- Only output the commit message, nothing else - -Git status: -$status_output - -Git diff: -$git_diff" + local prompt=$(build_commit_prompt "$git_diff" "$status_output") local json_payload if command -v jq &> /dev/null; then diff --git a/lib/providers/openai.zsh b/lib/providers/openai.zsh index a87239e..7d00329 100644 --- a/lib/providers/openai.zsh +++ b/lib/providers/openai.zsh @@ -2,6 +2,9 @@ # OpenAI provider for git-commit-ai +# Source the prompt builder +source "${0:A:h}/../prompt_builder.zsh" + openai_api_url="https://api.openai.com/v1/chat/completions" openai_model="gpt-4-turbo-preview" @@ -17,20 +20,7 @@ openai_generate_commit_message() { local git_diff="$1" local status_output="$2" - local prompt="You are a helpful assistant that writes concise and descriptive git commit messages. - -Based on the following git diff and status, generate a clear and concise commit message. -The commit message should: -- Be written in the imperative mood (e.g., 'Add feature' not 'Added feature') -- Be no longer than 72 characters -- Clearly describe what the change does, not how it does it -- Only output the commit message, nothing else - -Git status: -$status_output - -Git diff: -$git_diff" + local prompt=$(build_commit_prompt "$git_diff" "$status_output") local json_payload if command -v jq &> /dev/null; then diff --git a/tests/prompt_builder.test.zsh b/tests/prompt_builder.test.zsh new file mode 100644 index 0000000..0627149 --- /dev/null +++ b/tests/prompt_builder.test.zsh @@ -0,0 +1,79 @@ +#!/usr/bin/env zsh + +# Source test helper +source "${0:A:h}/test_helper.zsh" + +# Source the prompt builder +source "${0:A:h}/../lib/prompt_builder.zsh" + +# Test simple style (default) +test_simple_style() { + local diff="test diff" + local test_status="test status" + unset ZSH_GIT_AI_PROMPT + unset ZSH_GIT_AI_STYLE + unset ZSH_GIT_AI_MAX_LENGTH + + local result=$(build_commit_prompt "$diff" "$test_status") + + assert_contains "$result" "imperative mood" + assert_contains "$result" "72 characters" + assert_contains "$result" "Git status:" + assert_contains "$result" "test status" + assert_contains "$result" "Git diff:" + assert_contains "$result" "test diff" +} + +# Test conventional commits style +test_conventional_style() { + local diff="test diff" + local test_status="test status" + unset ZSH_GIT_AI_PROMPT + export ZSH_GIT_AI_STYLE="conventional" + export ZSH_GIT_AI_MAX_LENGTH="100" + + local result=$(build_commit_prompt "$diff" "$test_status") + + assert_contains "$result" "Conventional Commits" + assert_contains "$result" "(): " + assert_contains "$result" "100 characters" + assert_contains "$result" "feat, fix, docs" +} + +# Test semantic style +test_semantic_style() { + local diff="test diff" + local test_status="test status" + unset ZSH_GIT_AI_PROMPT + export ZSH_GIT_AI_STYLE="semantic" + export ZSH_GIT_AI_MAX_LENGTH="50" + + local result=$(build_commit_prompt "$diff" "$test_status") + + assert_contains "$result" "semantic prefix" + assert_contains "$result" "Add, Update, Fix, Remove" + assert_contains "$result" "50 characters" +} + +# Test custom prompt +test_custom_prompt() { + local diff="test diff" + local test_status="test status" + export ZSH_GIT_AI_PROMPT="Custom prompt for testing" + export ZSH_GIT_AI_STYLE="conventional" # Should be ignored + + local result=$(build_commit_prompt "$diff" "$test_status") + + assert_contains "$result" "Custom prompt for testing" + assert_contains "$result" "Git status:" + assert_contains "$result" "test status" + assert_not_contains "$result" "Conventional Commits" +} + +# Run tests +test_simple_style +test_conventional_style +test_semantic_style +test_custom_prompt + +echo "All prompt builder tests passed!" \ No newline at end of file diff --git a/tests/providers/customization.test.zsh b/tests/providers/customization.test.zsh new file mode 100644 index 0000000..fb9d6e8 --- /dev/null +++ b/tests/providers/customization.test.zsh @@ -0,0 +1,34 @@ +#!/usr/bin/env zsh + +# Source test helper +source "${0:A:h}/../test_helper.zsh" + +# Source the prompt builder directly +source "${0:A:h}/../../lib/prompt_builder.zsh" + +# Test that prompt builder is used with custom style +test_prompt_builder_custom_style() { + export ZSH_GIT_AI_STYLE="conventional" + unset ZSH_GIT_AI_PROMPT + + local result=$(build_commit_prompt "test diff" "test status") + + assert_contains "$result" "Conventional Commits" + assert_contains "$result" "feat, fix, docs" +} + +# Test that prompt builder is used with custom prompt +test_prompt_builder_custom_prompt() { + export ZSH_GIT_AI_PROMPT="My custom prompt template" + + local result=$(build_commit_prompt "test diff" "test status") + + assert_contains "$result" "My custom prompt template" + assert_contains "$result" "test status" +} + +# Run tests +test_prompt_builder_custom_style +test_prompt_builder_custom_prompt + +echo "All customization tests passed!" \ No newline at end of file