ruby_coded 0.2.0 → 0.2.2

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: 387a789f865a55493f5653662fb9bdd12225b8966be2670ab1ed4cfd281ce410
4
- data.tar.gz: d3d60bf8a6152d966dca9d4d7bd868e16c6df649c75ad8e0639d1774e9e8888b
3
+ metadata.gz: a1980b8f52a5fefcd383aec24380904430c10654b4cffe7bef6288b1b55e1eed
4
+ data.tar.gz: 1e54670c24db8f43ce9a0e4391889adf3d2d1c75f0923d43ff3260bc210e2e96
5
5
  SHA512:
6
- metadata.gz: 74c2dd8fc13a6ed1e9451982931fd4ae61c0343e2df652a753487b9e4a9d43fe08c9266dcc8baabcfd4670c546ae75a01ab36e95dcb0389186752d2b602030d0
7
- data.tar.gz: ea510740d48de995099240d5f1574b7fec383e3a90f4e75b30e4c5fd67975090dc8a7b25475fdced5fe7cec5398a0b01bf38e180ecd5e64b1f98dbb9fe14f1da
6
+ metadata.gz: c4d75ef0c6c7d0b3d1da17287b08bbdec2d9ee29bcc12c8551409ae2dbf2276562a10fc96294a9685b55a41f36d4711e448a640e97bb2711a9da9e0b1231e5ad
7
+ data.tar.gz: 75d2f43c84b9c8d027d17ac6d61b2f250fef6ce2dca88463b3ab580f74927bb16933b5a6f96eb7fed6b26e5138c621e9df8b56435f364dcbe3173c72f3a10114
data/.rubocop_todo.yml CHANGED
@@ -55,10 +55,12 @@ Metrics/AbcSize:
55
55
  Exclude:
56
56
  - 'lib/ruby_coded/chat/state.rb'
57
57
 
58
- # Offense count: 1
58
+ # Offense count: 3
59
59
  # Configuration parameters: CountComments, Max, CountAsOne.
60
60
  Metrics/ClassLength:
61
61
  Exclude:
62
+ - 'lib/ruby_coded/auth/auth_manager.rb'
63
+ - 'lib/ruby_coded/chat/app.rb'
62
64
  - 'lib/ruby_coded/chat/state.rb'
63
65
 
64
66
  # Offense count: 2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.2] - 2026-04-17
4
+
5
+ ### Fixed
6
+
7
+ - **OAuth credentials lost after in-TUI login**: When logging in to OpenAI via OAuth from within the chat (`/login`) while already authenticated with another provider (e.g. Anthropic) and using a non-Codex model, the freshly stored OAuth credentials were wiped from `~/.ruby_coded/config.yaml`. The root cause was two `UserConfig` instances holding independent in-memory copies of the config; when `ensure_valid_codex_model!` called `@user_config.set_config("model", "gpt-5.4")`, the stale hash (loaded before the OAuth login) was serialized back to disk, overwriting the OAuth credentials written moments earlier by `CredentialsStore`. Fixed by threading a single shared `UserConfig` through `Initializer`, `AuthManager`, `CredentialsStore` and `Chat::App`.
8
+
9
+ ## [0.2.1] - 2026-04-17
10
+
11
+ ### Fixed
12
+
13
+ - **Startup crash when stored model provider is not authenticated**: The CLI no longer raises `RubyLLM::ConfigurationError` at startup when the model saved in `~/.ruby_coded/config.yaml` belongs to a provider that has no credentials on the current machine (e.g. switching computers with only Anthropic authenticated but a GPT model stored). Instead, the app falls back to the default model of the authenticated provider and shows an in-chat system message suggesting `/login` or `/model` to adjust.
14
+
15
+ ### Added
16
+
17
+ - `AuthManager#provider_for_model` and `AuthManager#model_provider_authenticated?` helpers to detect the provider of a given model name and validate that its credentials are available.
18
+
3
19
  ## [0.2.0] - 2026-04-16
4
20
 
5
21
  ### Added
@@ -19,10 +35,10 @@
19
35
  - AuthManager skips OpenAI OAuth credentials for RubyLLM configuration (handled by CodexBridge)
20
36
  - Default OpenAI model updated to `gpt-5.4`
21
37
 
22
- ## [0.1.0] - 2026-04-15
23
-
24
- - Initial release
25
-
26
38
  ## [0.1.1] - 2026-04-15
27
39
 
28
40
  - Fix CI workflow
41
+
42
+ ## [0.1.0] - 2026-04-15
43
+
44
+ - Initial release
@@ -21,8 +21,9 @@ module RubyCoded
21
21
  anthropic: Providers::Anthropic
22
22
  }.freeze
23
23
 
24
- def initialize(config_path: UserConfig::CONFIG_PATH)
24
+ def initialize(config_path: UserConfig::CONFIG_PATH, user_config: nil)
25
25
  @config_path = config_path
26
+ @user_config = user_config
26
27
  end
27
28
 
28
29
  def login(provider_name)
@@ -48,6 +49,23 @@ module RubyCoded
48
49
  PROVIDERS.keys.select { |name| credential_store.retrieve(name) }
49
50
  end
50
51
 
52
+ def provider_for_model(model_name)
53
+ return nil if model_name.nil? || model_name.to_s.strip.empty?
54
+
55
+ normalized = model_name.to_s.downcase
56
+ return :openai if normalized.match?(/\A(gpt|o\d)/)
57
+ return :anthropic if normalized.start_with?("claude")
58
+
59
+ nil
60
+ end
61
+
62
+ def model_provider_authenticated?(model_name)
63
+ provider = provider_for_model(model_name)
64
+ return false unless provider
65
+
66
+ authenticated_provider_names.include?(provider)
67
+ end
68
+
51
69
  def check_authentication
52
70
  return if configured_providers.any? { |name| credential_store.retrieve(name) }
53
71
 
@@ -96,7 +114,7 @@ module RubyCoded
96
114
  end
97
115
 
98
116
  def credential_store
99
- @credential_store ||= CredentialsStore.new(config_path: @config_path)
117
+ @credential_store ||= CredentialsStore.new(config_path: @config_path, user_config: @user_config)
100
118
  end
101
119
 
102
120
  def extract_api_key(credentials)
@@ -8,8 +8,8 @@ module RubyCoded
8
8
  module Auth
9
9
  # This class is used to manage the credentials in the config file
10
10
  class CredentialsStore
11
- def initialize(config_path: UserConfig::CONFIG_PATH)
12
- @config = UserConfig.new(config_path: config_path)
11
+ def initialize(config_path: UserConfig::CONFIG_PATH, user_config: nil)
12
+ @config = user_config || UserConfig.new(config_path: config_path)
13
13
  end
14
14
 
15
15
  def store(provider_name, credentials)
@@ -29,13 +29,19 @@ module RubyCoded
29
29
  include LoginHandler
30
30
  include OAuthHandler
31
31
 
32
- def initialize(model:, user_config: nil, auth_manager: nil)
32
+ def initialize(model:, user_config: nil, auth_manager: nil, fallback_from_model: nil)
33
33
  @model = model
34
34
  @user_config = user_config
35
35
  @auth_manager = auth_manager
36
+ @fallback_from_model = fallback_from_model
36
37
  apply_plugin_extensions!
37
- @state = State.new(model: model)
38
- @credentials_store = Auth::CredentialsStore.new
38
+ build_components!
39
+ announce_model_fallback
40
+ end
41
+
42
+ def build_components!
43
+ @state = State.new(model: @model)
44
+ @credentials_store = Auth::CredentialsStore.new(user_config: @user_config)
39
45
  @llm_bridge = create_bridge
40
46
  @input_handler = InputHandler.new(@state)
41
47
  @command_handler = build_command_handler
@@ -94,6 +100,17 @@ module RubyCoded
94
100
  credentials_store: @credentials_store, auth_manager: @auth_manager)
95
101
  end
96
102
 
103
+ def announce_model_fallback
104
+ return unless @fallback_from_model && !@fallback_from_model.to_s.strip.empty?
105
+ return if @fallback_from_model == @model
106
+
107
+ @state.add_message(
108
+ :system,
109
+ "Model #{@fallback_from_model} is not available (provider not authenticated). " \
110
+ "Switched to #{@model}. Use /login to authenticate or /model to change."
111
+ )
112
+ end
113
+
97
114
  def apply_selected_model
98
115
  selected = @state.selected_model
99
116
  return @state.exit_model_select! unless selected
@@ -19,7 +19,8 @@ module RubyCoded
19
19
  def initialize
20
20
  @user_cfg = UserConfig.new
21
21
  @prompt = TTY::Prompt.new
22
- @auth_manager = Auth::AuthManager.new
22
+ @auth_manager = Auth::AuthManager.new(user_config: @user_cfg)
23
+ @fallback_from_model = nil
23
24
 
24
25
  ask_for_directory_permission unless @user_cfg.directory_trusted?
25
26
  @auth_manager.check_authentication
@@ -38,12 +39,22 @@ module RubyCoded
38
39
  end
39
40
 
40
41
  def start_chat
41
- Chat::App.new(model: resolved_chat_model, user_config: @user_cfg, auth_manager: @auth_manager).run
42
+ model = resolved_chat_model
43
+ Chat::App.new(
44
+ model: model,
45
+ user_config: @user_cfg,
46
+ auth_manager: @auth_manager,
47
+ fallback_from_model: @fallback_from_model
48
+ ).run
42
49
  end
43
50
 
44
51
  def resolved_chat_model
45
52
  stored = @user_cfg.get_config("model")
46
- return stored.to_s if stored && !stored.to_s.strip.empty?
53
+ if stored && !stored.to_s.strip.empty?
54
+ return stored.to_s if @auth_manager.model_provider_authenticated?(stored.to_s)
55
+
56
+ @fallback_from_model = stored.to_s
57
+ end
47
58
 
48
59
  provider = @auth_manager.authenticated_provider_names.first
49
60
  PROVIDER_DEFAULT_MODELS.fetch(provider, RubyLLM.config.default_model).to_s
@@ -2,7 +2,7 @@
2
2
 
3
3
  # This module contains the version of the RubyCoded gem
4
4
  module RubyCoded
5
- VERSION = "0.2.0"
5
+ VERSION = "0.2.2"
6
6
 
7
7
  def self.gem_version
8
8
  Gem::Version.new(VERSION).freeze
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_coded
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cesar Rodriguez
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-16 00:00:00.000000000 Z
11
+ date: 2026-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday