commitgpt 0.3.9 → 0.3.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d812f02e181a923e72486f7ffc5ac1d2e7614bd5eee7194bca7e3a764df7afda
4
- data.tar.gz: 7195e0edac02853ef4d1dcc7179664beffd023dacdb36c1133c5a01463a0175c
3
+ metadata.gz: bc50a5ed7cfb5cc59703014b3b6f58370028dfe2c1bc5c7186e3c6d43a755dd7
4
+ data.tar.gz: e9dd5c01f7c07062fc803710c79a31ce40ab9a9625918c2753bf59d25b4f9d4b
5
5
  SHA512:
6
- metadata.gz: 61f5d8a81bc519c7341dde8808656d729c8c3c6c826aa4ecb27779a5b59b3866f8f4fc671424139aa1fa801ce0d1bee14cac903363babafb082f5e3eb354904d
7
- data.tar.gz: a32a40428daef8ecfe051e39fc0f4cf4040755190c01f0e92eb747aed232b8cc4acabe9ba88b6a9d27b0b7edf666d2da96a07f7d0e75e80ca47adc33b1eb9227
6
+ metadata.gz: 2005518993b77ff65ab764075016d6c1a8bb1259efff645017ab3f0a23a83f03e1b18fc7df257f497a8ecf5a705d4a0c91c29a5e044d00e9896437faed7cf675
7
+ data.tar.gz: d290dac27335967df2b7dfdc8309509f7f228ede0d50c7e0b86e99c28cb16b1551d2bb5004ce3d846a183f1fb85beda193d5fec2873ad9e397211fa6e93ceb26
data/README.md CHANGED
@@ -261,13 +261,17 @@ Get an API key at [https://developer.amd.com.cn/radeon/tokenfactory](https://dev
261
261
 
262
262
  > **Note**: Registration requires **both an email address and a Chinese (+86) mobile phone number**. Without a Chinese phone number you cannot complete sign-up, so this provider is only usable if you have one.
263
263
 
264
+ > **Note**: The gateway in front of this endpoint always answers `GET /models` with 403, so models cannot be discovered automatically. CommitGPT ships the list below and offers it directly during setup — no detection request is made for this provider.
265
+
264
266
  ```
265
- DeepSeek-V4-Flash
266
- Qwen3.6-35B-A3B
267
- MiniCPM5-1B
268
- MiniCPM-V46
267
+ DeepSeek-V4-Flash # Max Context Length: 1,000,000 tokens
268
+ Qwen3.6-35B-A3B # Max Context Length: 262,144 tokens
269
+ MiniCPM-V46 # Max Context Length: 262,144 tokens
270
+ MiniCPM5-1B # Max Context Length: 131,072 tokens
269
271
  ```
270
272
 
273
+ Setup reports the selected model's context window and suggests a matching `diff_len`, so you don't have to work the number out yourself.
274
+
271
275
  ## How It Works
272
276
  This CLI tool runs a `git diff` command to grab all staged changes, sends this to OpenAI's GPT API (or compatible endpoint), and returns an AI-generated commit message. The tool uses the `/v1/chat/completions` endpoint with optimized prompts/system instructions for generating conventional commit messages.
273
277
 
@@ -3,7 +3,16 @@
3
3
  module CommitGpt
4
4
  # Provider presets for common AI providers
5
5
  PROVIDER_PRESETS = [
6
- { label: 'AMD Radeon (China)', value: 'amd', base_url: 'https://developer.amd.com.cn/radeon/api/v1' },
6
+ # The Azure gateway in front of this endpoint always answers /models with 403,
7
+ # so the model list is built in and no request is made during setup.
8
+ # Each value is that model's context window in tokens.
9
+ { label: 'AMD Radeon (China)', value: 'amd', base_url: 'https://developer.amd.com.cn/radeon/api/v1',
10
+ models: {
11
+ 'DeepSeek-V4-Flash' => 1_000_000,
12
+ 'MiniCPM-V46' => 262_144,
13
+ 'MiniCPM5-1B' => 131_072,
14
+ 'Qwen3.6-35B-A3B' => 262_144
15
+ } },
7
16
  { label: 'Anthropic Claude', value: 'anthropic', base_url: 'https://api.anthropic.com/v1' },
8
17
  { label: 'Cerebras', value: 'cerebras', base_url: 'https://api.cerebras.ai/v1' },
9
18
  { label: 'DeepSeek', value: 'deepseek', base_url: 'https://api.deepseek.com' },
@@ -10,6 +10,18 @@ require_relative 'string'
10
10
  module CommitGpt
11
11
  # Interactive setup wizard for configuring AI providers
12
12
  class SetupWizard
13
+ # Diff bytes per context token. Code and CJK pack more bytes into a token
14
+ # than English prose, so this stays deliberately conservative.
15
+ DIFF_BYTES_PER_TOKEN = 2
16
+
17
+ # Ceiling for the suggested diff length. Past this a single request gets
18
+ # slow and one commit message stops describing the change usefully, so
19
+ # chunked mode is the better answer than a bigger window.
20
+ MAX_SUGGESTED_DIFF_LEN = 131_072
21
+
22
+ # Used when the model's context window is unknown
23
+ FALLBACK_DIFF_LEN = 32_768
24
+
13
25
  def initialize
14
26
  @prompt = TTY::Prompt.new
15
27
  end
@@ -45,13 +57,13 @@ module CommitGpt
45
57
  provider = config['providers'].find { |p| p['name'] == selected }
46
58
 
47
59
  # Fetch models and let user select
48
- models = fetch_models_with_timeout(provider['base_url'], provider['api_key'])
60
+ models = fetch_models_with_timeout(provider['base_url'], provider['api_key'], preset_models(selected))
49
61
  return if models.nil?
50
62
 
51
63
  model = select_model(models, provider['model'])
52
64
 
53
65
  # Prompt for diff length
54
- diff_len = prompt_diff_len(provider['diff_len'] || 32_768)
66
+ diff_len = prompt_diff_len(default_diff_len(selected, model, provider['diff_len']))
55
67
 
56
68
  # Update config
57
69
  ConfigManager.update_provider(selected, { 'model' => model, 'diff_len' => diff_len })
@@ -75,7 +87,8 @@ module CommitGpt
75
87
  end
76
88
 
77
89
  # Fetch models and let user select
78
- models = fetch_models_with_timeout(provider_config['base_url'], provider_config['api_key'])
90
+ models = fetch_models_with_timeout(provider_config['base_url'], provider_config['api_key'],
91
+ preset_models(provider_config['name']))
79
92
  return if models.nil?
80
93
 
81
94
  model = select_model(models, provider_config['model'])
@@ -148,14 +161,14 @@ module CommitGpt
148
161
  return if api_key.nil? # User cancelled
149
162
 
150
163
  # Fetch models with timeout
151
- models = fetch_models_with_timeout(base_url, api_key)
164
+ models = fetch_models_with_timeout(base_url, api_key, preset_models(provider_name))
152
165
  return if models.nil?
153
166
 
154
167
  # Let user select model
155
168
  model = select_model(models, existing_provider&.dig('model'))
156
169
 
157
170
  # Prompt for diff length
158
- diff_len = prompt_diff_len(existing_provider&.dig('diff_len') || 32_768)
171
+ diff_len = prompt_diff_len(default_diff_len(provider_name, model, existing_provider&.dig('diff_len')))
159
172
 
160
173
  # Save configuration
161
174
  ConfigManager.update_provider(
@@ -181,14 +194,14 @@ module CommitGpt
181
194
  q.default 'http://localhost:8080/v1'
182
195
  end
183
196
 
184
- api_key = @prompt.mask('Enter your API key (optional):') { |q| q.echo false }
197
+ api_key = @prompt.mask('Enter your API key (optional):') { |q| q.echo false }.to_s
185
198
 
186
199
  # Fetch models
187
200
  models = fetch_models_with_timeout(base_url, api_key)
188
201
  return if models.nil?
189
202
 
190
203
  model = select_model(models)
191
- diff_len = prompt_diff_len(32_768)
204
+ diff_len = prompt_diff_len(FALLBACK_DIFF_LEN)
192
205
 
193
206
  # Add to presets dynamically (just for this session)
194
207
  # Save to config
@@ -230,20 +243,19 @@ module CommitGpt
230
243
 
231
244
  # Prompt for API key
232
245
  def prompt_api_key(_provider_name, existing_key)
233
- message = if existing_key && !existing_key.empty?
246
+ has_existing = existing_key && !existing_key.empty?
247
+
248
+ message = if has_existing
234
249
  'Enter your API key (press Enter to keep existing):'
235
250
  else
236
251
  'Enter your API key:'
237
252
  end
238
253
 
239
- key = @prompt.mask(message) { |q| q.echo false }
254
+ # mask returns nil, not '', when the input is empty
255
+ key = @prompt.mask(message) { |q| q.echo false }.to_s
240
256
 
241
257
  # If user pressed Enter and there's an existing key, use it
242
- if key.empty? && existing_key && !existing_key.empty?
243
- existing_key
244
- else
245
- key
246
- end
258
+ key.empty? && has_existing ? existing_key : key
247
259
  end
248
260
 
249
261
  # Ask how long to wait when fetching the model list, and persist it
@@ -262,12 +274,56 @@ module CommitGpt
262
274
  seconds
263
275
  end
264
276
 
265
- # Fetch models from provider with timeout
266
- def fetch_models_with_timeout(base_url, api_key)
277
+ # Built-in models a preset declares, as { model => context tokens }
278
+ def preset_model_table(provider_name)
279
+ models = PROVIDER_PRESETS.find { |p| p[:value] == provider_name }&.dig(:models)
280
+ models unless models.nil? || models.empty?
281
+ end
282
+
283
+ # Built-in model list for a preset, if it declares one
284
+ def preset_models(provider_name)
285
+ preset_model_table(provider_name)&.keys
286
+ end
287
+
288
+ # Context window of a built-in model, in tokens
289
+ def model_context_tokens(provider_name, model)
290
+ preset_model_table(provider_name)&.dig(model)
291
+ end
292
+
293
+ # Default diff length for a model, derived from its context window when
294
+ # known, and reported so the number is not a mystery. Numbers are printed
295
+ # undelimited because they get typed straight into the next prompt.
296
+ def default_diff_len(provider_name, model, existing)
297
+ context_tokens = model_context_tokens(provider_name, model)
298
+ return existing || FALLBACK_DIFF_LEN if context_tokens.nil?
299
+
300
+ fits = context_tokens * DIFF_BYTES_PER_TOKEN
301
+ suggested = [fits, MAX_SUGGESTED_DIFF_LEN].min
302
+ capped = ' (capped; larger diffs use chunked mode)' if suggested < fits
303
+
304
+ puts "\n#{model} context window: #{context_tokens} tokens (~#{fits} bytes of diff)".gray
305
+ puts " Suggested max diff length: #{suggested} bytes#{capped}".gray
306
+
307
+ # Only a value the user actually chose outranks the suggestion
308
+ customized = existing && existing != FALLBACK_DIFF_LEN
309
+ customized ? existing : suggested
310
+ end
311
+
312
+ # Fetch models from provider with timeout.
313
+ # A preset that declares :models has no usable /models endpoint, so its
314
+ # built-in list is offered directly and no request is made.
315
+ def fetch_models_with_timeout(base_url, api_key, builtin_models = nil)
316
+ if builtin_models
317
+ puts 'Using the built-in model list (this provider does not expose /models).'.gray
318
+ return builtin_models
319
+ end
320
+
267
321
  timeout = ConfigManager.get_model_fetch_timeout
268
322
  puts 'Fetching available models...'.gray
269
323
 
270
324
  models = nil
325
+ failure = nil
326
+
271
327
  begin
272
328
  Timeout.timeout(timeout) do
273
329
  headers = {
@@ -279,28 +335,23 @@ module CommitGpt
279
335
  response = HTTParty.get("#{base_url}/models", headers: headers)
280
336
 
281
337
  if response.code == 200
282
- models = response['data'] || []
283
- models = models.map { |m| m['id'] }.compact.sort
338
+ models = (response['data'] || []).map { |m| m['id'] }.compact.sort
284
339
  else
285
- puts "Failed to fetch models: HTTP #{response.code}".red
286
- return nil
340
+ failure = "Failed to fetch models: HTTP #{response.code}"
287
341
  end
288
342
  end
289
343
  rescue Timeout::Error
290
- puts "Connection timeout (#{timeout}s). Please check your network, base_url, and api_key.".red
291
- puts " Run 'aicm setup' again to raise the timeout.".gray
292
- exit(0)
344
+ failure = "Connection timeout (#{timeout}s). Please check your network, base_url, and api_key."
293
345
  rescue StandardError => e
294
- puts "Error fetching models: #{e.message}".red
295
- exit(0)
346
+ failure = "Error fetching models: #{e.message}"
296
347
  end
297
348
 
298
- if models.nil? || models.empty?
299
- puts '✖ No models found. Please check your configuration.'.red
300
- exit(0)
301
- end
349
+ failure ||= 'No models found. Please check your configuration.' if models.nil? || models.empty?
350
+ return models if failure.nil?
302
351
 
303
- models
352
+ puts "✖ #{failure}".red
353
+ puts " Run 'aicm setup' again to raise the timeout.".gray if failure.start_with?('Connection timeout')
354
+ exit(0)
304
355
  end
305
356
 
306
357
  # Let user select a model
@@ -328,7 +379,7 @@ module CommitGpt
328
379
  end
329
380
 
330
381
  # Prompt for diff length
331
- def prompt_diff_len(default = 32_768)
382
+ def prompt_diff_len(default = FALLBACK_DIFF_LEN)
332
383
  answer = @prompt.ask('Set the maximum diff length (Bytes) for generating commit message:') do |q|
333
384
  q.default default.to_s
334
385
  q.convert :int
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CommitGpt
4
- VERSION = '0.3.9'
4
+ VERSION = '0.3.10'
5
5
  end
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.9
4
+ version: 0.3.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peng Zhang