Module: AIGit::Config

Defined in:
lib/ai_git/config.rb

Constant Summary collapse

PROVIDER =
"llama_cpp"
DEFAULT_MODEL =
"ggml-org/gemma-4-E4B-it-GGUF:Q8_0"
DEFAULT_BASE_URL =
"http://127.0.0.1:8080"
ENDPOINT =
"/v1/chat/completions"
LOOPBACK_HOST =
/\A(localhost|127(\.\d{1,3}){3}|::1|0:0:0:0:0:0:0:1)\z/i.freeze
CONFIG_DIR_NAME =
".ai_git"
CONFIG_FILENAMES =
%w[config.yml config.yaml].freeze
SETTING_KEYS =
%w[model_name base_url no_color].freeze
TRUTHY_VALUES =
[true, "true", "yes", "on", "1"].freeze

Class Method Summary collapse

Class Method Details

.base_uriObject



123
124
125
126
127
128
129
130
# File 'lib/ai_git/config.rb', line 123

def base_uri
  uri = URI.parse(base_url)
  raise invalid_base_url_error unless valid_uri?(uri)

  uri
rescue URI::InvalidURIError
  raise invalid_base_url_error
end

.base_urlObject



109
110
111
# File 'lib/ai_git/config.rb', line 109

def base_url
  string_setting("base_url", DEFAULT_BASE_URL)
end

.config_dirObject



45
46
47
48
49
# File 'lib/ai_git/config.rb', line 45

def config_dir
  File.join(Dir.home, CONFIG_DIR_NAME)
rescue ArgumentError
  nil
end

.config_pathObject



51
52
53
54
55
56
# File 'lib/ai_git/config.rb', line 51

def config_path
  dir = config_dir
  return nil if dir.nil?

  CONFIG_FILENAMES.map { |name| File.join(dir, name) }.find { |path| File.file?(path) }
end

.endpointObject



119
120
121
# File 'lib/ai_git/config.rb', line 119

def endpoint
  ENDPOINT
end

.insecure_remote_base_url?Boolean

Returns:

  • (Boolean)


144
145
146
# File 'lib/ai_git/config.rb', line 144

def insecure_remote_base_url?
  !loopback_base_url? && base_uri.scheme == "http"
end

.invalid_base_url_errorObject



132
133
134
# File 'lib/ai_git/config.rb', line 132

def invalid_base_url_error
  "Invalid base_url #{base_url.inspect}: expected an http(s) URL. See `ai_git config`."
end

.load_settingsObject



66
67
68
69
70
71
# File 'lib/ai_git/config.rb', line 66

def load_settings
  path = config_path
  return {} if path.nil?

  validate_settings(parse_file(path), path)
end

.loopback_base_url?Boolean

Returns:

  • (Boolean)


140
141
142
# File 'lib/ai_git/config.rb', line 140

def loopback_base_url?
  base_uri.host.to_s.delete("[]").match?(LOOPBACK_HOST)
end

.model_nameObject



105
106
107
# File 'lib/ai_git/config.rb', line 105

def model_name
  string_setting("model_name", DEFAULT_MODEL)
end

.no_color?Boolean

Returns:

  • (Boolean)


113
114
115
116
117
# File 'lib/ai_git/config.rb', line 113

def no_color?
  value = settings["no_color"]
  value = value.downcase if value.is_a?(String)
  TRUTHY_VALUES.include?(value)
end

.parse_file(path) ⇒ Object



73
74
75
76
77
78
79
# File 'lib/ai_git/config.rb', line 73

def parse_file(path)
  YAML.safe_load_file(path) || {}
rescue Psych::SyntaxError => e
  raise "Invalid YAML in #{path}: #{e.message}"
rescue SystemCallError => e
  raise "Cannot read #{path}: #{e.message}"
end

.providerObject



41
42
43
# File 'lib/ai_git/config.rb', line 41

def provider
  PROVIDER
end

.reset!Object



62
63
64
# File 'lib/ai_git/config.rb', line 62

def reset!
  @settings = nil
end

.settingsObject



58
59
60
# File 'lib/ai_git/config.rb', line 58

def settings
  @settings ||= load_settings
end

.string_setting(key, default) ⇒ Object



92
93
94
95
96
97
98
99
# File 'lib/ai_git/config.rb', line 92

def string_setting(key, default)
  value = settings[key]
  return default if value.nil?

  raise "Invalid #{key} in #{config_path}: expected a non-empty string." unless string_value?(value)

  value.strip
end

.string_value?(value) ⇒ Boolean

Returns:

  • (Boolean)


101
102
103
# File 'lib/ai_git/config.rb', line 101

def string_value?(value)
  value.is_a?(String) && !value.strip.empty?
end

.valid_uri?(uri) ⇒ Boolean

Returns:

  • (Boolean)


136
137
138
# File 'lib/ai_git/config.rb', line 136

def valid_uri?(uri)
  %w[http https].include?(uri.scheme) && !uri.host.to_s.empty?
end

.validate_settings(data, path) ⇒ Object



81
82
83
84
85
86
87
88
89
90
# File 'lib/ai_git/config.rb', line 81

def validate_settings(data, path)
  raise "Invalid config in #{path}: expected a mapping of settings." unless data.is_a?(Hash)

  data = data.transform_keys(&:to_s)
  unknown = data.keys - SETTING_KEYS
  return data if unknown.empty?

  raise "Unknown setting#{'s' if unknown.length > 1} in #{path}: #{unknown.join(', ')}. " \
        "Known settings: #{SETTING_KEYS.join(', ')}."
end