commitgpt 0.3.3 → 0.3.4
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/commitgpt.gemspec +2 -2
- data/lib/commitgpt/commit_ai.rb +3 -120
- data/lib/commitgpt/diff_helpers.rb +149 -0
- data/lib/commitgpt/provider_presets.rb +8 -7
- data/lib/commitgpt/string.rb +19 -0
- data/lib/commitgpt/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: de7ab665c77f18d08715d16d56b7f4cd0a3b7e677bdbeabb80b25e96afcc8f47
|
|
4
|
+
data.tar.gz: f3cddebeaf3d1f479155e85a9039f47a14c7cc61d0390968c47fb0e810ca6ce7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9576ab5271ad85fb136eab275b987787cac5bae9af6c3458563089dd4e782c92ce57b48630379ad7c8ebbd11a816a0f2d6b69d302557dc8f5e3f69b482d2b889
|
|
7
|
+
data.tar.gz: e882ab4a80976721e87fcee2d5935021c2063ddf23c61e1cb26a45c7e8ead43da33610fb1f18cbd21fbc17d540cbdbdc7d9f52cc7641c666d589ce2fd41e15b2
|
data/commitgpt.gemspec
CHANGED
|
@@ -27,8 +27,8 @@ Gem::Specification.new do |spec|
|
|
|
27
27
|
spec.require_paths = ['lib']
|
|
28
28
|
|
|
29
29
|
# Uncomment to register a new dependency of your gem
|
|
30
|
-
spec.add_dependency
|
|
31
|
-
spec.add_dependency
|
|
30
|
+
spec.add_dependency 'httparty', '~> 0.24'
|
|
31
|
+
spec.add_dependency 'thor', '~> 1.4'
|
|
32
32
|
spec.add_dependency 'tty-prompt', '~> 0.23'
|
|
33
33
|
|
|
34
34
|
# For more information and examples about making a new gem, checkout our
|
data/lib/commitgpt/commit_ai.rb
CHANGED
|
@@ -8,11 +8,14 @@ require 'io/console'
|
|
|
8
8
|
require 'tty-prompt'
|
|
9
9
|
require_relative 'string'
|
|
10
10
|
require_relative 'config_manager'
|
|
11
|
+
require_relative 'diff_helpers'
|
|
11
12
|
|
|
12
13
|
# CommitGpt based on GPT-3
|
|
13
14
|
module CommitGpt
|
|
14
15
|
# Commit AI roboter based on GPT-3
|
|
15
16
|
class CommitAi
|
|
17
|
+
include DiffHelpers
|
|
18
|
+
|
|
16
19
|
attr_reader :api_key, :base_url, :model, :diff_len
|
|
17
20
|
|
|
18
21
|
def initialize
|
|
@@ -127,126 +130,6 @@ module CommitGpt
|
|
|
127
130
|
generate_commit(diff)
|
|
128
131
|
end
|
|
129
132
|
|
|
130
|
-
def git_diff
|
|
131
|
-
exclusions = '":(exclude)Gemfile.lock" ":(exclude)package-lock.json" ":(exclude)yarn.lock" ":(exclude)pnpm-lock.yaml"'
|
|
132
|
-
diff_cached = `git diff --cached . #{exclusions}`.chomp
|
|
133
|
-
diff_unstaged = `git diff . #{exclusions}`.chomp
|
|
134
|
-
|
|
135
|
-
if !diff_unstaged.empty?
|
|
136
|
-
if diff_cached.empty?
|
|
137
|
-
# Scenario: Only unstaged changes
|
|
138
|
-
choice = prompt_no_staged_changes
|
|
139
|
-
case choice
|
|
140
|
-
when :add_all
|
|
141
|
-
puts '▲ Running git add .'.yellow
|
|
142
|
-
system('git add .')
|
|
143
|
-
diff_cached = `git diff --cached . #{exclusions}`.chomp
|
|
144
|
-
if diff_cached.empty?
|
|
145
|
-
puts '▲ Still no changes to commit.'.red
|
|
146
|
-
return nil
|
|
147
|
-
end
|
|
148
|
-
when :exit
|
|
149
|
-
return nil
|
|
150
|
-
end
|
|
151
|
-
else
|
|
152
|
-
# Scenario: Mixed state (some staged, some not)
|
|
153
|
-
puts '▲ You have both staged and unstaged changes:'.yellow
|
|
154
|
-
|
|
155
|
-
staged_files = `git diff --cached --name-status . #{exclusions}`.chomp
|
|
156
|
-
unstaged_files = `git diff --name-status . #{exclusions}`.chomp
|
|
157
|
-
|
|
158
|
-
puts "\n #{'Staged changes:'.green}"
|
|
159
|
-
puts staged_files.gsub(/^/, ' ')
|
|
160
|
-
|
|
161
|
-
puts "\n #{'Unstaged changes:'.red}"
|
|
162
|
-
puts unstaged_files.gsub(/^/, ' ')
|
|
163
|
-
puts ''
|
|
164
|
-
|
|
165
|
-
prompt = TTY::Prompt.new
|
|
166
|
-
choice = prompt.select('How to proceed?') do |menu|
|
|
167
|
-
menu.choice 'Include unstaged changes (git add .)', :add_all
|
|
168
|
-
menu.choice 'Use staged changes only', :staged_only
|
|
169
|
-
menu.choice 'Exit', :exit
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
case choice
|
|
173
|
-
when :add_all
|
|
174
|
-
puts '▲ Running git add .'.yellow
|
|
175
|
-
system('git add .')
|
|
176
|
-
diff_cached = `git diff --cached . #{exclusions}`.chomp
|
|
177
|
-
when :exit
|
|
178
|
-
return nil
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
elsif diff_cached.empty?
|
|
182
|
-
# Scenario: No changes at all (staged or unstaged)
|
|
183
|
-
# Check if there are ANY unstaged files (maybe untracked?)
|
|
184
|
-
# git status --porcelain includes untracked files
|
|
185
|
-
git_status = `git status --porcelain`.chomp
|
|
186
|
-
if git_status.empty?
|
|
187
|
-
puts '▲ No changes to commit. Working tree clean.'.yellow
|
|
188
|
-
return nil
|
|
189
|
-
else
|
|
190
|
-
# Only untracked files? Or ignored files?
|
|
191
|
-
# If diff_unstaged is empty but git status is not, it usually means untracked files.
|
|
192
|
-
# Let's offer to add them too.
|
|
193
|
-
choice = prompt_no_staged_changes
|
|
194
|
-
case choice
|
|
195
|
-
when :add_all
|
|
196
|
-
puts '▲ Running git add .'.yellow
|
|
197
|
-
system('git add .')
|
|
198
|
-
diff_cached = `git diff --cached . #{exclusions}`.chomp
|
|
199
|
-
when :exit
|
|
200
|
-
return nil
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
diff = diff_cached
|
|
206
|
-
|
|
207
|
-
if diff.length > @diff_len
|
|
208
|
-
choice = prompt_diff_handling(diff.length, @diff_len)
|
|
209
|
-
case choice
|
|
210
|
-
when :truncate
|
|
211
|
-
puts "▲ Truncating diff to #{@diff_len} chars...".yellow
|
|
212
|
-
diff = diff[0...@diff_len]
|
|
213
|
-
when :unlimited
|
|
214
|
-
puts "▲ Using full diff (#{diff.length} chars)...".yellow
|
|
215
|
-
when :exit
|
|
216
|
-
return nil
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
diff
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
def prompt_no_staged_changes
|
|
224
|
-
puts '▲ No staged changes found (but unstaged/untracked files exist).'.yellow
|
|
225
|
-
prompt = TTY::Prompt.new
|
|
226
|
-
begin
|
|
227
|
-
prompt.select('Choose an option:') do |menu|
|
|
228
|
-
menu.choice "Run 'git add .' to stage all changes", :add_all
|
|
229
|
-
menu.choice 'Exit (stage files manually)', :exit
|
|
230
|
-
end
|
|
231
|
-
rescue TTY::Reader::InputInterrupt, Interrupt
|
|
232
|
-
:exit
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def prompt_diff_handling(current_len, max_len)
|
|
237
|
-
puts "▲ The diff is too large (#{current_len} chars, max #{max_len}).".yellow
|
|
238
|
-
prompt = TTY::Prompt.new
|
|
239
|
-
begin
|
|
240
|
-
prompt.select('Choose an option:') do |menu|
|
|
241
|
-
menu.choice "Use first #{max_len} characters to generate commit message", :truncate
|
|
242
|
-
menu.choice 'Use unlimited characters (may fail or be slow)', :unlimited
|
|
243
|
-
menu.choice 'Exit', :exit
|
|
244
|
-
end
|
|
245
|
-
rescue TTY::Reader::InputInterrupt, Interrupt
|
|
246
|
-
:exit
|
|
247
|
-
end
|
|
248
|
-
end
|
|
249
|
-
|
|
250
133
|
def welcome
|
|
251
134
|
puts "\n▲ Welcome to AI Commits!".green
|
|
252
135
|
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommitGpt
|
|
4
|
+
# Helper methods for handling git diffs
|
|
5
|
+
# rubocop:disable Metrics/ModuleLength
|
|
6
|
+
module DiffHelpers
|
|
7
|
+
# Lock files to exclude from diff but detect changes
|
|
8
|
+
LOCK_FILES = %w[Gemfile.lock package-lock.json yarn.lock pnpm-lock.yaml].freeze
|
|
9
|
+
|
|
10
|
+
def git_diff
|
|
11
|
+
exclusions = LOCK_FILES.map { |f| "\":(exclude)#{f}\"" }.join(' ')
|
|
12
|
+
diff_cached = `git diff --cached . #{exclusions}`.chomp
|
|
13
|
+
diff_unstaged = `git diff . #{exclusions}`.chomp
|
|
14
|
+
|
|
15
|
+
# Detect lock file changes and build summary
|
|
16
|
+
@lock_file_summary = detect_lock_file_changes
|
|
17
|
+
|
|
18
|
+
if !diff_unstaged.empty?
|
|
19
|
+
if diff_cached.empty?
|
|
20
|
+
# Scenario: Only unstaged changes
|
|
21
|
+
choice = prompt_no_staged_changes
|
|
22
|
+
case choice
|
|
23
|
+
when :add_all
|
|
24
|
+
puts '▲ Running git add .'.yellow
|
|
25
|
+
system('git add .')
|
|
26
|
+
diff_cached = `git diff --cached . #{exclusions}`.chomp
|
|
27
|
+
if diff_cached.empty?
|
|
28
|
+
puts '▲ Still no changes to commit.'.red
|
|
29
|
+
return nil
|
|
30
|
+
end
|
|
31
|
+
when :exit
|
|
32
|
+
return nil
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
# Scenario: Mixed state (some staged, some not)
|
|
36
|
+
puts '▲ You have both staged and unstaged changes:'.yellow
|
|
37
|
+
|
|
38
|
+
staged_files = `git diff --cached --name-status . #{exclusions}`.chomp
|
|
39
|
+
unstaged_files = `git diff --name-status . #{exclusions}`.chomp
|
|
40
|
+
|
|
41
|
+
puts "\n #{'Staged changes:'.green}"
|
|
42
|
+
puts staged_files.gsub(/^/, ' ')
|
|
43
|
+
|
|
44
|
+
puts "\n #{'Unstaged changes:'.red}"
|
|
45
|
+
puts unstaged_files.gsub(/^/, ' ')
|
|
46
|
+
puts ''
|
|
47
|
+
|
|
48
|
+
prompt = TTY::Prompt.new
|
|
49
|
+
choice = prompt.select('How to proceed?') do |menu|
|
|
50
|
+
menu.choice 'Include unstaged changes (git add .)', :add_all
|
|
51
|
+
menu.choice 'Use staged changes only', :staged_only
|
|
52
|
+
menu.choice 'Exit', :exit
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
case choice
|
|
56
|
+
when :add_all
|
|
57
|
+
puts '▲ Running git add .'.yellow
|
|
58
|
+
system('git add .')
|
|
59
|
+
diff_cached = `git diff --cached . #{exclusions}`.chomp
|
|
60
|
+
when :exit
|
|
61
|
+
return nil
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
elsif diff_cached.empty?
|
|
65
|
+
# Scenario: No changes at all (staged or unstaged)
|
|
66
|
+
# Check if there are ANY unstaged files (maybe untracked?)
|
|
67
|
+
# git status --porcelain includes untracked files
|
|
68
|
+
git_status = `git status --porcelain`.chomp
|
|
69
|
+
if git_status.empty?
|
|
70
|
+
puts '▲ No changes to commit. Working tree clean.'.yellow
|
|
71
|
+
return nil
|
|
72
|
+
else
|
|
73
|
+
# Only untracked files? Or ignored files?
|
|
74
|
+
# If diff_unstaged is empty but git status is not, it usually means untracked files.
|
|
75
|
+
# Let's offer to add them too.
|
|
76
|
+
choice = prompt_no_staged_changes
|
|
77
|
+
case choice
|
|
78
|
+
when :add_all
|
|
79
|
+
puts '▲ Running git add .'.yellow
|
|
80
|
+
system('git add .')
|
|
81
|
+
diff_cached = `git diff --cached . #{exclusions}`.chomp
|
|
82
|
+
when :exit
|
|
83
|
+
return nil
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
diff = diff_cached
|
|
89
|
+
|
|
90
|
+
# Prepend lock file summary to diff if present
|
|
91
|
+
diff = "#{@lock_file_summary}\n\n#{diff}" if @lock_file_summary
|
|
92
|
+
|
|
93
|
+
if diff.length > diff_len
|
|
94
|
+
choice = prompt_diff_handling(diff.length, diff_len)
|
|
95
|
+
case choice
|
|
96
|
+
when :truncate
|
|
97
|
+
puts "▲ Truncating diff to #{diff_len} chars...".yellow
|
|
98
|
+
diff = diff[0...diff_len]
|
|
99
|
+
when :unlimited
|
|
100
|
+
puts "▲ Using full diff (#{diff.length} chars)...".yellow
|
|
101
|
+
when :exit
|
|
102
|
+
return nil
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
diff
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def prompt_no_staged_changes
|
|
110
|
+
puts '▲ No staged changes found (but unstaged/untracked files exist).'.yellow
|
|
111
|
+
prompt = TTY::Prompt.new
|
|
112
|
+
begin
|
|
113
|
+
prompt.select('Choose an option:') do |menu|
|
|
114
|
+
menu.choice "Run 'git add .' to stage all changes", :add_all
|
|
115
|
+
menu.choice 'Exit (stage files manually)', :exit
|
|
116
|
+
end
|
|
117
|
+
rescue TTY::Reader::InputInterrupt, Interrupt
|
|
118
|
+
:exit
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def prompt_diff_handling(current_len, max_len)
|
|
123
|
+
puts "▲ The diff is too large (#{current_len} chars, max #{max_len}).".yellow
|
|
124
|
+
prompt = TTY::Prompt.new
|
|
125
|
+
begin
|
|
126
|
+
prompt.select('Choose an option:') do |menu|
|
|
127
|
+
menu.choice "Use first #{max_len} characters to generate commit message", :truncate
|
|
128
|
+
menu.choice 'Use unlimited characters (may fail or be slow)', :unlimited
|
|
129
|
+
menu.choice 'Exit', :exit
|
|
130
|
+
end
|
|
131
|
+
rescue TTY::Reader::InputInterrupt, Interrupt
|
|
132
|
+
:exit
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def detect_lock_file_changes
|
|
137
|
+
# Check both staged and unstaged changes for lock files
|
|
138
|
+
staged_files = `git diff --cached --name-only`.chomp.split("\n")
|
|
139
|
+
unstaged_files = `git diff --name-only`.chomp.split("\n")
|
|
140
|
+
changed_files = (staged_files + unstaged_files).uniq
|
|
141
|
+
|
|
142
|
+
updated_locks = LOCK_FILES.select { |lock| changed_files.include?(lock) }
|
|
143
|
+
return nil if updated_locks.empty?
|
|
144
|
+
|
|
145
|
+
updated_locks.map { |f| "#{f} updated (dependency changes)" }.join(', ')
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
# rubocop:enable Metrics/ModuleLength
|
|
149
|
+
end
|
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
module CommitGpt
|
|
4
4
|
# Provider presets for common AI providers
|
|
5
5
|
PROVIDER_PRESETS = [
|
|
6
|
+
{ label: 'Anthropic (Claude)', value: 'anthropic', base_url: 'https://api.anthropic.com/v1' },
|
|
6
7
|
{ label: 'Cerebras', value: 'cerebras', base_url: 'https://api.cerebras.ai/v1' },
|
|
7
|
-
{ label: '
|
|
8
|
-
{ label: '
|
|
8
|
+
{ label: 'DeepSeek', value: 'deepseek', base_url: 'https://api.deepseek.com' },
|
|
9
|
+
{ label: 'Google AI', value: 'gemini', base_url: 'https://generativelanguage.googleapis.com/v1beta/openai' },
|
|
10
|
+
{ label: 'Groq', value: 'groq', base_url: 'https://api.groq.com/openai/v1' },
|
|
9
11
|
{ label: 'LLaMa.cpp', value: 'llamacpp', base_url: 'http://127.0.0.1:8080/v1' },
|
|
10
12
|
{ label: 'LM Studio', value: 'lmstudio', base_url: 'http://127.0.0.1:1234/v1' },
|
|
11
13
|
{ label: 'Llamafile', value: 'llamafile', base_url: 'http://127.0.0.1:8080/v1' },
|
|
12
|
-
{ label: 'DeepSeek', value: 'deepseek', base_url: 'https://api.deepseek.com' },
|
|
13
|
-
{ label: 'Groq', value: 'groq', base_url: 'https://api.groq.com/openai/v1' },
|
|
14
14
|
{ label: 'Mistral', value: 'mistral', base_url: 'https://api.mistral.ai/v1' },
|
|
15
|
-
{ label: '
|
|
16
|
-
{ label: '
|
|
17
|
-
{ label: '
|
|
15
|
+
{ label: 'NVIDIA NIM', value: 'nvidia_nim', base_url: 'https://integrate.api.nvidia.com/v1' },
|
|
16
|
+
{ label: 'Ollama', value: 'ollama', base_url: 'http://127.0.0.1:11434/v1' },
|
|
17
|
+
{ label: 'OpenAI', value: 'openai', base_url: 'https://api.openai.com/v1' },
|
|
18
|
+
{ label: 'OpenRouter', value: 'openrouter', base_url: 'https://openrouter.ai/api/v1' }
|
|
18
19
|
].freeze
|
|
19
20
|
end
|
data/lib/commitgpt/string.rb
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'tty-prompt'
|
|
4
|
+
|
|
5
|
+
# Monkey patch TTY::Prompt::List to add padding at the bottom of menus
|
|
6
|
+
module TTY
|
|
7
|
+
class Prompt
|
|
8
|
+
# Monkey patch to add padding
|
|
9
|
+
class List
|
|
10
|
+
alias original_render_menu render_menu
|
|
11
|
+
|
|
12
|
+
# Override render_menu to add empty lines at the bottom
|
|
13
|
+
def render_menu
|
|
14
|
+
output = original_render_menu
|
|
15
|
+
# Add 2 empty lines at the bottom so menu doesn't stick to terminal edge
|
|
16
|
+
"#{output}\n\n"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
3
22
|
# Open String to add color
|
|
4
23
|
class String
|
|
5
24
|
def red
|
data/lib/commitgpt/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: commitgpt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peng Zhang
|
|
@@ -68,6 +68,7 @@ files:
|
|
|
68
68
|
- lib/commitgpt/cli.rb
|
|
69
69
|
- lib/commitgpt/commit_ai.rb
|
|
70
70
|
- lib/commitgpt/config_manager.rb
|
|
71
|
+
- lib/commitgpt/diff_helpers.rb
|
|
71
72
|
- lib/commitgpt/provider_presets.rb
|
|
72
73
|
- lib/commitgpt/setup_wizard.rb
|
|
73
74
|
- lib/commitgpt/string.rb
|