ai-commit-message 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +21 -0
- data/README.md +59 -21
- data/lib/ai-commit-message/cli.rb +58 -16
- data/lib/ai-commit-message/config_manager.rb +4 -1
- data/lib/ai-commit-message/suggester.rb +124 -21
- metadata +13 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cf7ae3da6684c33dd9b0518bda5c3d0f85398132e1089a51a442bf88892e4b7d
|
|
4
|
+
data.tar.gz: d3d27482ca167d55bffb77c25ebd0553eea74fc93fdf5554bd6a434e80926aeb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 07d58b95afcdde4e24d7f85e15354d4d437cecbb92ddfc16b68e8a8d13bf2a10a576674b43000e163b0465021947cc6c5a8210894a5583bdb00e278e7600e641
|
|
7
|
+
data.tar.gz: b22d685bb2687103cb3b414f6fc0b002dd92007b377b76ee9a89a7d99e50a052184d641277ac636d46e56780cc8265282350cd40dbb5e3515ba8418a0f8aaca1
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Afonso Neto
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# AI git commit message
|
|
2
2
|
|
|
3
|
-
A Ruby gem that automatically generates concise and meaningful git commit messages using
|
|
3
|
+
A Ruby gem that automatically generates concise and meaningful git commit messages using a local LLM via [Ollama](https://github.com/ollama/ollama), LM Studio, llama.cpp `llama-server`, or any other OpenAI-compatible API.
|
|
4
|
+
|
|
5
|
+

|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
@@ -26,13 +28,13 @@ bundle install
|
|
|
26
28
|
|
|
27
29
|
- Ruby 3.0 or newer
|
|
28
30
|
- Git repository
|
|
29
|
-
- [Ollama](https://github.com/ollama/ollama)
|
|
31
|
+
- A local LLM server: [Ollama](https://github.com/ollama/ollama), [LM Studio](https://lmstudio.ai/), [llama.cpp `llama-server`](https://github.com/ggml-org/llama.cpp), or any other OpenAI-compatible endpoint
|
|
30
32
|
|
|
31
33
|
## Usage
|
|
32
34
|
|
|
33
35
|
### Basic Usage
|
|
34
36
|
|
|
35
|
-
Generate a commit message for your staged changes:
|
|
37
|
+
Generate a commit message for your staged changes and commit with it after a confirmation:
|
|
36
38
|
|
|
37
39
|
```bash
|
|
38
40
|
ai-commit-message commit
|
|
@@ -43,18 +45,39 @@ This will:
|
|
|
43
45
|
2. Review your recent commit history for style consistency
|
|
44
46
|
3. Consider your current branch name
|
|
45
47
|
4. Generate an appropriate commit message
|
|
48
|
+
5. Ask for confirmation and create the commit (answering "no" keeps the printed message so you can edit or copy it)
|
|
49
|
+
|
|
50
|
+
### Message only
|
|
51
|
+
|
|
52
|
+
If you just want the suggested message without the confirmation prompt — e.g. to pipe it somewhere or review it yourself — use `--message-only`:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
ai-commit-message commit --message-only
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Conventional Commits
|
|
59
|
+
|
|
60
|
+
Ask the model to follow the [Conventional Commits](https://www.conventionalcommits.org/) format (`feat:`, `fix:`, `chore:`, ...):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
ai-commit-message commit --conventional
|
|
64
|
+
```
|
|
46
65
|
|
|
47
66
|
### Configuration
|
|
48
67
|
|
|
49
|
-
|
|
68
|
+
Configure the API URL and model interactively:
|
|
50
69
|
|
|
51
70
|
```bash
|
|
52
71
|
ai-commit-message config
|
|
53
72
|
```
|
|
54
73
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
74
|
+
If your server is reachable, the model prompt becomes a picker populated with the models available on that server.
|
|
75
|
+
|
|
76
|
+
List the models available on your server:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
ai-commit-message models
|
|
80
|
+
```
|
|
58
81
|
|
|
59
82
|
### Command Line Options
|
|
60
83
|
|
|
@@ -64,44 +87,59 @@ Override configuration settings directly:
|
|
|
64
87
|
ai-commit-message commit --url=http://your-api-endpoint --model=your-model-name
|
|
65
88
|
```
|
|
66
89
|
|
|
90
|
+
All options:
|
|
91
|
+
|
|
92
|
+
| Option | Description | Default |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| `--url` | Base URL of the OpenAI-compatible API | `http://localhost:11434` |
|
|
95
|
+
| `--model` | Model name | `qwen3:8b` |
|
|
96
|
+
| `--length` | Maximum message length in characters | `72` |
|
|
97
|
+
| `--conventional` | Use Conventional Commits format | off |
|
|
98
|
+
| `--message-only` | Print the message without the commit confirmation prompt | off |
|
|
99
|
+
|
|
67
100
|
## How It Works
|
|
68
101
|
|
|
69
102
|
The gem:
|
|
70
|
-
1. Collects your staged changes using `git diff --cached`
|
|
103
|
+
1. Collects your staged changes using `git diff --cached` (capped at 12,000 characters so large diffs don't overwhelm a local model's context)
|
|
71
104
|
2. Retrieves your recent commit history for context
|
|
72
105
|
3. Identifies your current branch name
|
|
73
|
-
4. Sends
|
|
74
|
-
5.
|
|
106
|
+
4. Sends a chat request to `{url}/v1/chat/completions` — the standard OpenAI-compatible endpoint
|
|
107
|
+
5. Cleans the model output (strips code fences, quotes and reasoning blocks) and prints a single-line commit message (limited to 72 characters by default)
|
|
108
|
+
|
|
109
|
+
The URL accepts base addresses with or without a trailing `/v1` and `/`, so `http://localhost:11434` (Ollama), `http://localhost:1234/v1` (LM Studio) and `http://localhost:8080` (llama.cpp `llama-server`) all work as-is.
|
|
75
110
|
|
|
76
111
|
## Configuration File
|
|
77
112
|
|
|
78
113
|
The gem stores your configuration in `~/.ai-commit-message.conf`. You can manually edit this file if needed. Example:
|
|
79
114
|
|
|
80
115
|
```bash
|
|
81
|
-
url=http://localhost:11434
|
|
82
|
-
model=
|
|
116
|
+
url=http://localhost:11434
|
|
117
|
+
model=qwen3:8b
|
|
83
118
|
```
|
|
84
119
|
|
|
85
120
|
## Models
|
|
86
121
|
|
|
87
|
-
The default configuration uses Ollama with the `
|
|
122
|
+
The default configuration uses Ollama with the `qwen3:8b` model, but you can use any model available through your API endpoint.
|
|
88
123
|
|
|
89
124
|
### Recommended Models
|
|
90
125
|
|
|
91
|
-
- **
|
|
92
|
-
- **
|
|
93
|
-
- **
|
|
126
|
+
- **qwen3:8b**: Good balance of quality and speed (default). Note that it is a thinking model, so expect a few seconds of reasoning before the message appears
|
|
127
|
+
- **qwen2.5-coder:7b**: Faster, non-thinking coding model
|
|
128
|
+
- **qwen3.5:4b**: Current-generation small model for modest hardware
|
|
129
|
+
- **gemma4:e4b**: Compact general-purpose option
|
|
130
|
+
- **llama3.1:8b**: Solid general-purpose choice
|
|
94
131
|
|
|
95
132
|
## Contributing
|
|
96
133
|
|
|
97
134
|
1. Fork the repository
|
|
98
135
|
2. Create your feature branch: `git checkout -b my-new-feature`
|
|
99
136
|
3. Install dependencies: `bundle install`
|
|
100
|
-
4.
|
|
101
|
-
5.
|
|
102
|
-
6.
|
|
103
|
-
7.
|
|
137
|
+
4. Run the tests: `bundle exec rake test`
|
|
138
|
+
5. Make your changes and add tests if applicable
|
|
139
|
+
6. Commit your changes: `git commit -m 'Add some feature'`
|
|
140
|
+
7. Push to the branch: `git push origin my-new-feature`
|
|
141
|
+
8. Submit a pull request
|
|
104
142
|
|
|
105
143
|
## License
|
|
106
144
|
|
|
107
|
-
This gem is available as open source
|
|
145
|
+
This gem is available as open source under the terms of the [MIT License](LICENSE).
|
|
@@ -5,25 +5,66 @@ require_relative 'config_manager'
|
|
|
5
5
|
|
|
6
6
|
module AiCommitMessage
|
|
7
7
|
class CLI < Thor
|
|
8
|
+
def self.exit_on_failure?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
|
|
8
12
|
DEFAULT_URL = 'http://localhost:11434'
|
|
9
|
-
DEFAULT_MODEL_NAME = '
|
|
13
|
+
DEFAULT_MODEL_NAME = 'qwen3:8b'
|
|
14
|
+
MAX_DIFF_LENGTH = 12_000
|
|
10
15
|
|
|
11
|
-
desc "commit", "Generate
|
|
12
|
-
method_option :url, type: :string
|
|
13
|
-
method_option :model, type: :string
|
|
16
|
+
desc "commit", "Generate a commit message and commit with it after confirmation"
|
|
17
|
+
method_option :url, type: :string
|
|
18
|
+
method_option :model, type: :string
|
|
19
|
+
method_option :length, type: :numeric, default: Suggester::DEFAULT_LENGTH
|
|
20
|
+
method_option :conventional, type: :boolean, default: false
|
|
21
|
+
method_option :message_only, type: :boolean, default: false
|
|
14
22
|
|
|
15
23
|
def commit
|
|
16
24
|
git_diff_output = `git diff --cached --no-color`
|
|
25
|
+
if git_diff_output.strip.empty?
|
|
26
|
+
warn 'Nothing is staged. Stage your changes with `git add` first.'
|
|
27
|
+
exit 1
|
|
28
|
+
end
|
|
29
|
+
|
|
17
30
|
git_log_output = `git log --format=%s -n 30`
|
|
18
31
|
git_current_branch = `git branch --show-current`
|
|
19
32
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
33
|
+
suggester = AiCommitMessage::Suggester.new(git_diff_output[0, MAX_DIFF_LENGTH], git_log_output, git_current_branch)
|
|
34
|
+
commit_message = suggester.generate_commit_message(
|
|
35
|
+
url: url_to_be_used(options[:url]),
|
|
36
|
+
model: model_to_be_used(options[:model]),
|
|
37
|
+
length: options[:length],
|
|
38
|
+
conventional: options[:conventional]
|
|
39
|
+
)
|
|
25
40
|
|
|
26
41
|
puts commit_message
|
|
42
|
+
return if options[:message_only]
|
|
43
|
+
|
|
44
|
+
prompt = TTY::Prompt.new
|
|
45
|
+
begin
|
|
46
|
+
# tty-prompt takes the default on EOF, which would silently commit
|
|
47
|
+
# in non-interactive runs; treat a closed stdin as "no".
|
|
48
|
+
confirmed = !$stdin.eof? && prompt.yes?("Commit with this message?", default: true)
|
|
49
|
+
rescue TTY::Reader::InputInterrupt
|
|
50
|
+
confirmed = false
|
|
51
|
+
end
|
|
52
|
+
return unless confirmed
|
|
53
|
+
|
|
54
|
+
system('git', 'commit', '-m', commit_message)
|
|
55
|
+
rescue AiCommitMessage::Suggester::Error => e
|
|
56
|
+
warn e.message
|
|
57
|
+
exit 1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
desc "models", "List models available on the configured API URL"
|
|
61
|
+
method_option :url, type: :string
|
|
62
|
+
|
|
63
|
+
def models
|
|
64
|
+
puts AiCommitMessage::Suggester.list_models(url_to_be_used(options[:url]))
|
|
65
|
+
rescue AiCommitMessage::Suggester::Error => e
|
|
66
|
+
warn e.message
|
|
67
|
+
exit 1
|
|
27
68
|
end
|
|
28
69
|
|
|
29
70
|
desc "config", "Set global configs. API URL and Model name"
|
|
@@ -31,7 +72,12 @@ module AiCommitMessage
|
|
|
31
72
|
prompt = TTY::Prompt.new
|
|
32
73
|
|
|
33
74
|
url = prompt.ask("API URL:", default: ConfigManager.get_url || DEFAULT_URL)
|
|
34
|
-
|
|
75
|
+
available_models = AiCommitMessage::Suggester.list_models(url) rescue nil
|
|
76
|
+
model = if available_models && !available_models.empty?
|
|
77
|
+
prompt.select("Model name:", available_models)
|
|
78
|
+
else
|
|
79
|
+
prompt.ask("Model name:", default: ConfigManager.get_model || DEFAULT_MODEL_NAME)
|
|
80
|
+
end
|
|
35
81
|
|
|
36
82
|
ConfigManager.set_url(url)
|
|
37
83
|
ConfigManager.set_model(model)
|
|
@@ -42,15 +88,11 @@ module AiCommitMessage
|
|
|
42
88
|
private
|
|
43
89
|
|
|
44
90
|
def url_to_be_used(options_url)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
ConfigManager.get_url || DEFAULT_URL
|
|
91
|
+
options_url || ConfigManager.get_url || DEFAULT_URL
|
|
48
92
|
end
|
|
49
93
|
|
|
50
94
|
def model_to_be_used(options_model)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
ConfigManager.get_model || DEFAULT_MODEL_NAME
|
|
95
|
+
options_model || ConfigManager.get_model || DEFAULT_MODEL_NAME
|
|
54
96
|
end
|
|
55
97
|
end
|
|
56
98
|
end
|
|
@@ -34,5 +34,8 @@ class ConfigManager
|
|
|
34
34
|
@config
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
def self.save_config
|
|
37
|
+
def self.save_config
|
|
38
|
+
File.write(CONFIG_FILE, config.map { |key, value| "#{key}=#{value}" }.join("\n"))
|
|
39
|
+
FileUtils.chmod(0600, CONFIG_FILE)
|
|
40
|
+
end
|
|
38
41
|
end
|
|
@@ -1,41 +1,144 @@
|
|
|
1
1
|
require 'net/http'
|
|
2
2
|
require 'uri'
|
|
3
3
|
require 'json'
|
|
4
|
+
require 'openssl'
|
|
4
5
|
|
|
5
6
|
module AiCommitMessage
|
|
6
7
|
class Suggester
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
class ConnectionError < Error; end
|
|
10
|
+
class ApiError < Error; end
|
|
11
|
+
|
|
12
|
+
OPEN_TIMEOUT = 5
|
|
13
|
+
READ_TIMEOUT = 120
|
|
14
|
+
DEFAULT_LENGTH = 72
|
|
15
|
+
# Thinking models (qwen3 and friends) spend tokens reasoning before the
|
|
16
|
+
# answer; the budget must leave room for both or content comes back empty.
|
|
17
|
+
MAX_TOKENS = 2048
|
|
18
|
+
|
|
19
|
+
def self.list_models(url)
|
|
20
|
+
uri = URI("#{normalized_base(url)}/v1/models")
|
|
21
|
+
http = build_http(uri)
|
|
22
|
+
|
|
23
|
+
begin
|
|
24
|
+
response = http.request(Net::HTTP::Get.new(uri))
|
|
25
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError
|
|
26
|
+
raise ConnectionError,
|
|
27
|
+
"Could not connect to #{uri.host}:#{uri.port}. Is your local LLM server running?"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
parse_response(response).fetch('data', []).map { |m| m['id'] }.compact.sort
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.parse_response(response)
|
|
34
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
35
|
+
raise ApiError, "API returned HTTP #{response.code}: #{response.body.to_s[0, 200]}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
JSON.parse(response.body)
|
|
39
|
+
rescue JSON::ParserError
|
|
40
|
+
raise ApiError, 'API response was not valid JSON'
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.build_http(uri)
|
|
44
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
45
|
+
http.use_ssl = uri.scheme == 'https'
|
|
46
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
47
|
+
http.open_timeout = OPEN_TIMEOUT
|
|
48
|
+
http.read_timeout = READ_TIMEOUT
|
|
49
|
+
http
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.normalized_base(url)
|
|
53
|
+
url.chomp('/').sub(%r{/v1$}, '')
|
|
54
|
+
end
|
|
55
|
+
|
|
7
56
|
def initialize(git_diff_output, git_log_output, git_current_branch)
|
|
8
57
|
@git_diff_output = git_diff_output
|
|
9
58
|
@git_log_output = git_log_output
|
|
10
59
|
@git_current_branch = git_current_branch
|
|
11
60
|
end
|
|
12
61
|
|
|
13
|
-
def generate_commit_message(url:, model:)
|
|
14
|
-
|
|
15
|
-
|
|
62
|
+
def generate_commit_message(url:, model:, length: DEFAULT_LENGTH, conventional: false)
|
|
63
|
+
response = post_json(chat_completions_uri(url), request_body(model, length, conventional))
|
|
64
|
+
json = self.class.parse_response(response)
|
|
65
|
+
message = clean_commit_message(json.dig('choices', 0, 'message', 'content'), length)
|
|
66
|
+
return message unless message.empty?
|
|
67
|
+
|
|
68
|
+
raise ApiError,
|
|
69
|
+
'Model returned no commit message content. Try a non-thinking model or a different model.'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def request_body(model, length, conventional)
|
|
75
|
+
{
|
|
16
76
|
model: model,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
77
|
+
messages: [
|
|
78
|
+
{ role: 'system', content: system_prompt(conventional) },
|
|
79
|
+
{ role: 'user', content: user_prompt(length) }
|
|
80
|
+
],
|
|
81
|
+
temperature: 0.3,
|
|
82
|
+
max_tokens: MAX_TOKENS,
|
|
83
|
+
stream: false
|
|
21
84
|
}
|
|
22
|
-
|
|
23
|
-
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def system_prompt(conventional)
|
|
88
|
+
prompt = 'You write git commit messages. Respond with a single line containing ' \
|
|
89
|
+
'only the commit message: no quotes, no backticks, no markdown, no explanations.'
|
|
90
|
+
prompt += ' Use the Conventional Commits format (type: description).' if conventional
|
|
91
|
+
prompt
|
|
92
|
+
end
|
|
24
93
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
94
|
+
def user_prompt(length)
|
|
95
|
+
<<~PROMPT
|
|
96
|
+
Write a concise git commit message with no more than #{length} characters for the staged changes below.
|
|
97
|
+
Follow the style of these recent commit messages: #{@git_log_output}
|
|
98
|
+
#{branch_line}
|
|
99
|
+
Git diff:
|
|
100
|
+
#{@git_diff_output}
|
|
101
|
+
PROMPT
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# git branch --show-current returns nothing on a detached HEAD
|
|
105
|
+
def branch_line
|
|
106
|
+
@git_current_branch.strip.empty? ? '' : "The current branch name is: #{@git_current_branch.strip}"
|
|
107
|
+
end
|
|
108
|
+
# Models tend to wrap answers in code fences or quotes, inline their
|
|
109
|
+
# reasoning in <think> blocks, or offer alternatives; reduce everything to
|
|
110
|
+
# a single clean subject line. Control characters are stripped so that a
|
|
111
|
+
# prompt-injected model cannot smuggle terminal escapes into git history.
|
|
112
|
+
def clean_commit_message(raw, length)
|
|
113
|
+
line = raw.to_s
|
|
114
|
+
.gsub(%r{<think>.*?</think>}m, '')
|
|
115
|
+
.gsub(/\e\][^\x07\e]*(?:\x07|\e\\)/, '')
|
|
116
|
+
.gsub(/\e\[[0-9;:]*[A-Za-z]/, '')
|
|
117
|
+
.gsub(/[\e\a\b\v\f\r]/, '')
|
|
118
|
+
.gsub('`', '')
|
|
119
|
+
.each_line.map(&:strip).reject(&:empty?).first.to_s
|
|
120
|
+
line = line.sub(/\A["'](.*)["']\z/, '\1')
|
|
121
|
+
line.gsub(/\s+/, ' ')[0, length].to_s.strip
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Accepts base URLs with or without a trailing /v1 and with or without a
|
|
125
|
+
# trailing slash, e.g. http://localhost:11434, http://localhost:1234/v1/
|
|
126
|
+
def chat_completions_uri(url)
|
|
127
|
+
URI("#{self.class.normalized_base(url)}/v1/chat/completions")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def post_json(uri, body)
|
|
131
|
+
http = self.class.build_http(uri)
|
|
132
|
+
request = Net::HTTP::Post.new(uri)
|
|
133
|
+
request['Content-Type'] = 'application/json'
|
|
134
|
+
request.body = body.to_json
|
|
30
135
|
|
|
31
136
|
begin
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
137
|
+
http.request(request)
|
|
138
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError => e
|
|
139
|
+
raise ConnectionError,
|
|
140
|
+
"Could not connect to #{uri.host}:#{uri.port}. Is your local LLM server running?"
|
|
36
141
|
end
|
|
37
|
-
|
|
38
|
-
nil
|
|
39
142
|
end
|
|
40
143
|
end
|
|
41
|
-
end
|
|
144
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ai-commit-message
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Afonso Neto
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: thor
|
|
@@ -39,13 +38,13 @@ dependencies:
|
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
39
|
version: '0'
|
|
41
40
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
41
|
+
name: rake
|
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
|
44
43
|
requirements:
|
|
45
44
|
- - ">="
|
|
46
45
|
- !ruby/object:Gem::Version
|
|
47
46
|
version: '0'
|
|
48
|
-
type: :
|
|
47
|
+
type: :development
|
|
49
48
|
prerelease: false
|
|
50
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
50
|
requirements:
|
|
@@ -53,20 +52,21 @@ dependencies:
|
|
|
53
52
|
- !ruby/object:Gem::Version
|
|
54
53
|
version: '0'
|
|
55
54
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
55
|
+
name: minitest
|
|
57
56
|
requirement: !ruby/object:Gem::Requirement
|
|
58
57
|
requirements:
|
|
59
58
|
- - ">="
|
|
60
59
|
- !ruby/object:Gem::Version
|
|
61
60
|
version: '0'
|
|
62
|
-
type: :
|
|
61
|
+
type: :development
|
|
63
62
|
prerelease: false
|
|
64
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
64
|
requirements:
|
|
66
65
|
- - ">="
|
|
67
66
|
- !ruby/object:Gem::Version
|
|
68
67
|
version: '0'
|
|
69
|
-
description: Git commit messages suggested by AI
|
|
68
|
+
description: Git commit messages suggested by AI using a local LLM via any OpenAI-compatible
|
|
69
|
+
API
|
|
70
70
|
email:
|
|
71
71
|
- afonso.pontesneto@gmail.com
|
|
72
72
|
executables:
|
|
@@ -74,6 +74,7 @@ executables:
|
|
|
74
74
|
extensions: []
|
|
75
75
|
extra_rdoc_files: []
|
|
76
76
|
files:
|
|
77
|
+
- LICENSE
|
|
77
78
|
- README.md
|
|
78
79
|
- bin/ai-commit-message
|
|
79
80
|
- lib/ai-commit-message/cli.rb
|
|
@@ -81,9 +82,9 @@ files:
|
|
|
81
82
|
- lib/ai-commit-message/suggester.rb
|
|
82
83
|
homepage: https://github.com/AfonsoNeto/ai-commit-message
|
|
83
84
|
licenses:
|
|
84
|
-
-
|
|
85
|
-
metadata:
|
|
86
|
-
|
|
85
|
+
- MIT
|
|
86
|
+
metadata:
|
|
87
|
+
rubygems_mfa_required: 'true'
|
|
87
88
|
rdoc_options: []
|
|
88
89
|
require_paths:
|
|
89
90
|
- lib
|
|
@@ -98,8 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
98
99
|
- !ruby/object:Gem::Version
|
|
99
100
|
version: '0'
|
|
100
101
|
requirements: []
|
|
101
|
-
rubygems_version:
|
|
102
|
-
signing_key:
|
|
102
|
+
rubygems_version: 4.0.12
|
|
103
103
|
specification_version: 4
|
|
104
104
|
summary: Git commit messages suggested by AI
|
|
105
105
|
test_files: []
|