Skip to content
Open
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
34 changes: 25 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ through the OpenRouter API.
- Analyzes staged changes in your git repository
- Generates contextual commit messages using AI
- Supports multiple AI models via [OpenRouter](https://openrouter.ai)
- Configurable AI model and temperature settings
- Optional emoji prefixes in commit messages
- Optionally edit generated commit message
- Automatically commits changes with generated message, if confirmed

Expand Down Expand Up @@ -56,21 +58,35 @@ $ acmsg config set api_token <your_api_token>

## Usage

### Commit

```bash
acmsg commit
```
usage: acmsg [-h] [--version] {commit,config} ...

Automated commit message generator
Generate a commit message for staged changes.

Options:
- `--model MODEL`: Specify the AI model (overrides config)
- `--temperature TEMP`: Set model temperature (0.0-2.0, overrides config)
- `--use-emojis`: Enable emoji prefixes in commit messages
- `--no-use-emojis`: Disable emoji prefixes in commit messages

positional arguments:
{commit,config} Commands
commit generate a commit message
config manage configuration settings
### Config

options:
-h, --help show this help message and exit
--version display the program version and exit
```bash
acmsg config set <parameter> <value>
acmsg config get <parameter>
```

Manage configuration settings.

Available parameters:
- `api_token`: OpenRouter API key
- `model`: AI model to use (default: qwen/qwen3-30b-a3b:free)
- `temperature`: Model temperature (0.0-2.0, default: 0.8)
- `use_emojis`: Enable emoji prefixes (true/false, default: false)

## License

acmsg is licenced under the MIT License, as included in the [LICENSE](LICENSE) file.
9 changes: 6 additions & 3 deletions src/acmsg/cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,14 @@ def ensure_api_token_configured(config):
return api_token


def generate_commit_message(repo, api_token, model, temperature):
def generate_commit_message(repo, api_token, model, temperature, use_emojis):
"""Generate a commit message and return it formatted."""
stop_spinner = threading.Event()
spinner_thread = threading.Thread(target=spinner, args=(stop_spinner,))
spinner_thread.start()

try:
generator = CommitMessageGenerator(api_token, model, temperature)
generator = CommitMessageGenerator(api_token, model, temperature, use_emojis)
message = generator.generate(repo.files_status, repo.diff)
finally:
stop_spinner.set()
Expand Down Expand Up @@ -173,13 +173,14 @@ def handle_commit(args: Any) -> None:
api_token = ensure_api_token_configured(cfg)
model = args.model or cfg.model
temperature = args.temperature or cfg.temperature
use_emojis = args.use_emojis if hasattr(args, "use_emojis") and args.use_emojis is not None else cfg.use_emojis

repo = GitUtils()
if not repo.files_status or not repo.diff:
print(Fore.YELLOW + "Nothing to commit." + Style.RESET_ALL)
sys.exit(1)

formatted_message = generate_commit_message(repo, api_token, model, temperature)
formatted_message = generate_commit_message(repo, api_token, model, temperature, use_emojis)
print_message(formatted_message)

while True:
Expand Down Expand Up @@ -226,6 +227,8 @@ def handle_config(args: Any) -> None:
print(cfg._default_model)
elif args.parameter == "temperature":
print(cfg._default_temperature)
elif args.parameter == "use_emojis":
print(cfg._default_use_emojis)
elif args.parameter == "api_token":
print(f"{Fore.YELLOW}API token not set.{Style.RESET_ALL}")
except ConfigError as e:
Expand Down
15 changes: 13 additions & 2 deletions src/acmsg/cli/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ def create_parser() -> argparse.ArgumentParser:
type=float,
help="specify the temperature for the AI model (overrides config)",
)
commit_parser.add_argument(
"--use-emojis",
action="store_true",
help="enable emoji prefixes in commit messages (overrides config)",
)
commit_parser.add_argument(
"--no-use-emojis",
action="store_false",
dest="use_emojis",
help="disable emoji prefixes in commit messages (overrides config)",
)

# Config command parser
config_parser = subparsers.add_parser(
Expand All @@ -57,7 +68,7 @@ def create_parser() -> argparse.ArgumentParser:
)
config_set.add_argument(
"parameter",
choices=["api_token", "model", "temperature"],
choices=["api_token", "model", "temperature", "use_emojis"],
help="parameter name",
)
config_set.add_argument("value", type=str, help="Value")
Expand All @@ -70,7 +81,7 @@ def create_parser() -> argparse.ArgumentParser:
)
config_get.add_argument(
"parameter",
choices=["api_token", "model", "temperature"],
choices=["api_token", "model", "temperature", "use_emojis"],
help="parameter name",
)

Expand Down
1 change: 1 addition & 0 deletions src/acmsg/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

DEFAULT_MODEL = "qwen/qwen3-30b-a3b:free"
DEFAULT_TEMPERATURE = 0.8
DEFAULT_USE_EMOJIS = False
API_ENDPOINT = "https://openrouter.ai/api/v1/chat/completions"
CONFIG_FILENAME = "config.yaml"
CONFIG_DIR = "acmsg"
18 changes: 17 additions & 1 deletion src/acmsg/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from pathlib import Path
from typing import Optional, Any

from ..constants import DEFAULT_MODEL, DEFAULT_TEMPERATURE, CONFIG_FILENAME, CONFIG_DIR
from ..constants import DEFAULT_MODEL, DEFAULT_TEMPERATURE, DEFAULT_USE_EMOJIS, CONFIG_FILENAME, CONFIG_DIR
from ..exceptions import ConfigError
from ..templates import renderer

Expand All @@ -17,6 +17,7 @@ def __init__(self):
"""Initialize the Config instance with configuration values."""
self._default_model = DEFAULT_MODEL
self._default_temperature = DEFAULT_TEMPERATURE
self._default_use_emojis = DEFAULT_USE_EMOJIS
self._config_file = self._init_config_file()
self._load_config()

Expand Down Expand Up @@ -49,6 +50,7 @@ def _load_config(self) -> None:
self._temperature = (
self._validate_temperature(temp_value) or self._default_temperature
)
self._use_emojis = data.get("use_emojis") if data.get("use_emojis") is not None else self._default_use_emojis

self._api_token = data.get("api_token")
except Exception as e:
Expand Down Expand Up @@ -103,6 +105,15 @@ def api_token(self) -> Optional[str]:
"""
return self._api_token

@property
def use_emojis(self) -> bool:
"""Get the configured emoji support setting.

Returns:
Boolean indicating if emoji support is enabled
"""
return self._use_emojis

@property
def config_file(self) -> Path:
"""Get the path to the configuration file.
Expand Down Expand Up @@ -135,6 +146,11 @@ def set_parameter(self, parameter: str, value: Any) -> None:
self._model = value
elif parameter == "temperature":
self._temperature = self._validate_temperature(value)
elif parameter == "use_emojis":
if isinstance(value, str):
self._use_emojis = value.lower() in ("true", "yes", "1", "on")
else:
self._use_emojis = bool(value)
elif parameter == "api_token":
self._api_token = value
except Exception as e:
Expand Down
6 changes: 4 additions & 2 deletions src/acmsg/core/generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,22 @@
class CommitMessageGenerator:
"""Generate commit messages from git changes."""

def __init__(self, api_token: str, model: str, temperature: float):
def __init__(self, api_token: str, model: str, temperature: float, use_emojis: bool = False):
"""Initialize the commit message generator.

Args:
api_token: OpenRouter API token
model: Model ID to use for generation
temperature: Temperature to use for generation
use_emojis: Whether to enable emoji support
"""
if not api_token:
raise AcmsgError("API token is required")

self._api_client = OpenRouterClient(api_token)
self._model = model
self._temperature = temperature
self._use_emojis = use_emojis

def generate(self, git_status: str, git_diff: str) -> str:
"""Generate a commit message from git status and diff.
Expand All @@ -37,7 +39,7 @@ def generate(self, git_status: str, git_diff: str) -> str:
AcmsgError: If the generation fails
"""
system_prompt = renderer.render_system_prompt()
user_prompt = renderer.render_user_prompt(status=git_status, diff=git_diff)
user_prompt = renderer.render_user_prompt(status=git_status, diff=git_diff, use_emojis=self._use_emojis)

return self._api_client.generate_completion(
model=self._model,
Expand Down
4 changes: 3 additions & 1 deletion src/acmsg/core/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ def _get_git_status(self) -> str:
["git", "diff", "--cached", "--name-status"],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
check=True,
)
return output.stdout.strip().replace("\t", " ")
Expand All @@ -84,7 +86,7 @@ def _get_git_diff(self) -> str:
"""
try:
output = subprocess.run(
["git", "diff", "--cached"], capture_output=True, text=True, check=True
["git", "diff", "--cached"], capture_output=True, text=True, encoding="utf-8", errors="replace", check=True
)
return output.stdout.strip()
except subprocess.CalledProcessError as e:
Expand Down
14 changes: 13 additions & 1 deletion src/acmsg/templates/assets/system_prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,26 @@ You are an expert at writing Git commits, analyzing diffs to identify significan
- Prioritize clarity over comprehensiveness when multiple changes exist

## COMMIT MESSAGE RULES
1. Subject line MUST use format: `<type>[(optional scope)][!]: <description>`
1. Subject line MUST use format: `<emoji_prefix> <type>[(optional scope)][!]: <description>` where `<emoji_prefix>` is ONLY included when emoji support is enabled
- Types: `feat` (new feature), `fix` (bug fix/enhancement), `chore`, `ci`, `docs`, `style`, `refactor`, `perf`, `test`
- Use `feat` and `fix` when applicable; other types only when more appropriate
- Use `fix` for minor enhancements; reserve `feat` for significant new functionality
- Scope MUST be a noun describing codebase section: e.g., `fix(parser):`
- Add `!` before colon to indicate breaking changes
- Description SHOULD be 50-70 characters, MUST NOT end with period

2. Emoji prefixes (use ONLY when emoji support is enabled):
- ✨ feat: new feature
- 🐛 fix: bug fix/enhancement
- 📝 docs: documentation
- 💄 style: code style/formatting
- ♻️ refactor: code refactoring
- ⚡ perf: performance improvements
- ✅ test: testing
- 📦 chore: maintenance tasks
- 👷 ci: CI/CD
- 🚀 build: build system

2. Body (optional)
- Begin one blank line after description
- Format as paragraphs, not bulleted/numbered/hyphenated lists
Expand Down
3 changes: 3 additions & 0 deletions src/acmsg/templates/assets/template_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ api_token: # Required

# Optionally, set the temperature:
# temperature: 0.8 # default

# Optionally, enable emoji prefixes in commit messages:
# use_emojis: false # default
8 changes: 8 additions & 0 deletions src/acmsg/templates/assets/user_prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ Generate a commit message describing the changes in this git diff.
- Limit body to ~200 characters unless complexity requires more detail
- Omit body entirely if the subject line captures the change adequately

## EMOJI SUPPORT
{% if use_emojis %}
Emoji prefixes are ENABLED - include appropriate emoji before the commit type
(e.g., ✨ feat: Add new feature, 🐛 fix: Resolve issue)
{% else %}
Emoji prefixes are DISABLED - do not include any emojis in the commit message
{% endif %}

## INPUT
File statuses:
{{ status }}
Expand Down
5 changes: 3 additions & 2 deletions src/acmsg/templates/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,18 @@ def render_system_prompt(self) -> str:
"""Render the system prompt template."""
return self._system_prompt_template.render()

def render_user_prompt(self, status: str, diff: str) -> str:
def render_user_prompt(self, status: str, diff: str, use_emojis: bool = False) -> str:
"""Render the user prompt template with git status and diff.

Args:
status: Output of git status command
diff: Output of git diff command
use_emojis: Whether to enable emoji support

Returns:
Rendered user prompt template
"""
return self._user_prompt_template.render(status=status, diff=diff)
return self._user_prompt_template.render(status=status, diff=diff, use_emojis=use_emojis)

def render_config_template(self) -> str:
"""Render the configuration template.
Expand Down