Module: Legion::Extensions::Llm::CredentialSources
- Defined in:
- lib/legion/extensions/llm/credential_sources.rb
Overview
Read-only helpers that provider gems use to probe common credential locations (env vars, Claude config, Codex auth, Legion settings, and network probes). All methods are pure readers — the calling provider decides what to do with the result.
Constant Summary collapse
- CLAUDE_SETTINGS =
File.('~/.claude/settings.json')
- CLAUDE_PROJECT =
File.join(Dir.pwd, '.claude', 'settings.json')
- CODEX_AUTH =
File.('~/.codex/auth.json')
Class Method Summary collapse
-
.claude_config ⇒ Object
Merged Claude config (user-level + project-level).
-
.claude_config_value(key) ⇒ Object
Read a single key from the merged Claude config, trying both symbol and string variants.
-
.claude_env_value(key) ⇒ Object
Read a key from the :env hash inside Claude config, trying both symbol and string variants.
-
.codex_openai_key ⇒ Object
Read the OPENAI_API_KEY from ~/.codex/auth.json.
-
.codex_token ⇒ Object
Read the bearer token from ~/.codex/auth.json when auth_mode is “chatgpt” and the JWT is not expired.
-
.credential_hash(config) ⇒ Object
SHA-256 hex digest of the first credential value found in the config hash (checks api_key, bearer_token, access_token in order).
-
.dedup_credentials(candidates) ⇒ Object
Deduplicate credential configs by the SHA-256 of their credential value (api_key / bearer_token / access_token).
-
.env(key) ⇒ Object
Fetch an environment variable, stripping whitespace.
-
.http_ok?(url, path:, timeout: 2) ⇒ Boolean
HTTP GET probe via Faraday.
-
.localhost?(url) ⇒ Boolean
Returns true when the URL points to localhost / 127.0.0.1 / ::1.
-
.setting(*path) ⇒ Object
Dig into Legion::Settings, returning nil if the module is not loaded or the path doesn’t exist.
-
.socket_open?(host, port, timeout: 0.1) ⇒ Boolean
TCP connect probe with a short timeout.
Class Method Details
.claude_config ⇒ Object
Merged Claude config (user-level + project-level). Project settings override user settings. Memoized for the lifetime of the process.
32 33 34 |
# File 'lib/legion/extensions/llm/credential_sources.rb', line 32 def claude_config @claude_config ||= merge_claude_configs end |
.claude_config_value(key) ⇒ Object
Read a single key from the merged Claude config, trying both symbol and string variants.
38 39 40 41 |
# File 'lib/legion/extensions/llm/credential_sources.rb', line 38 def claude_config_value(key) cfg = claude_config cfg[key.to_sym] || cfg[key.to_s] end |
.claude_env_value(key) ⇒ Object
Read a key from the :env hash inside Claude config, trying both symbol and string variants.
45 46 47 48 49 50 |
# File 'lib/legion/extensions/llm/credential_sources.rb', line 45 def claude_env_value(key) env_hash = claude_config_value(:env) return nil unless env_hash.is_a?(Hash) env_hash[key.to_sym] || env_hash[key.to_s] end |
.codex_openai_key ⇒ Object
Read the OPENAI_API_KEY from ~/.codex/auth.json.
67 68 69 70 71 72 73 74 |
# File 'lib/legion/extensions/llm/credential_sources.rb', line 67 def codex_openai_key data = read_json(CODEX_AUTH) val = data[:OPENAI_API_KEY] || data['OPENAI_API_KEY'] return nil if val.nil? stripped = val.to_s.strip stripped.empty? ? nil : stripped end |
.codex_token ⇒ Object
Read the bearer token from ~/.codex/auth.json when auth_mode is “chatgpt” and the JWT is not expired.
54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/legion/extensions/llm/credential_sources.rb', line 54 def codex_token data = read_json(CODEX_AUTH) mode = data[:auth_mode] || data['auth_mode'] return nil unless mode == 'chatgpt' token = data[:bearer_token] || data['bearer_token'] return nil if token.nil? || token.to_s.strip.empty? return nil unless token_valid?(token) token end |
.credential_hash(config) ⇒ Object
SHA-256 hex digest of the first credential value found in the config hash (checks api_key, bearer_token, access_token in order). Returns nil when no credential field is present.
152 153 154 155 156 157 158 159 |
# File 'lib/legion/extensions/llm/credential_sources.rb', line 152 def credential_hash(config) val = config[:api_key] || config['api_key'] || config[:bearer_token] || config['bearer_token'] || config[:access_token] || config['access_token'] return nil if val.nil? Digest::SHA256.hexdigest(val.to_s) end |
.dedup_credentials(candidates) ⇒ Object
Deduplicate credential configs by the SHA-256 of their credential value (api_key / bearer_token / access_token). First source wins. Entries without a credential value are always kept.
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/legion/extensions/llm/credential_sources.rb', line 132 def dedup_credentials(candidates) seen = {} result = {} candidates.each do |instance_id, config| hash = credential_hash(config) if hash.nil? result[instance_id] = config elsif !seen.key?(hash) seen[hash] = instance_id result[instance_id] = config end end result end |
.env(key) ⇒ Object
Fetch an environment variable, stripping whitespace. Returns nil when the variable is unset or blank.
22 23 24 25 26 27 28 |
# File 'lib/legion/extensions/llm/credential_sources.rb', line 22 def env(key) val = ENV.fetch(key, nil) return nil if val.nil? stripped = val.strip stripped.empty? ? nil : stripped end |
.http_ok?(url, path:, timeout: 2) ⇒ Boolean
HTTP GET probe via Faraday. Returns true only on a 2xx status.
114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/legion/extensions/llm/credential_sources.rb', line 114 def http_ok?(url, path:, timeout: 2) require 'faraday' conn = Faraday.new(url: url) do |f| f..timeout = timeout f..open_timeout = timeout end response = conn.get(path) response.status >= 200 && response.status < 300 rescue StandardError false ensure conn&.close if conn.respond_to?(:close) end |
.localhost?(url) ⇒ Boolean
Returns true when the URL points to localhost / 127.0.0.1 / ::1.
162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/legion/extensions/llm/credential_sources.rb', line 162 def localhost?(url) return false if url.nil? uri = URI.parse(url.to_s) host = uri.host return false if host.nil? normalized = host.delete_prefix('[').delete_suffix(']') %w[localhost 127.0.0.1 ::1].include?(normalized) rescue URI::InvalidURIError false end |
.setting(*path) ⇒ Object
Dig into Legion::Settings, returning nil if the module is not loaded or the path doesn’t exist.
78 79 80 81 82 83 84 |
# File 'lib/legion/extensions/llm/credential_sources.rb', line 78 def setting(*path) return nil unless defined?(::Legion::Settings) ::Legion::Settings.dig(*path) rescue StandardError nil end |
.socket_open?(host, port, timeout: 0.1) ⇒ Boolean
TCP connect probe with a short timeout. Returns true if the port is reachable, false otherwise.
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/legion/extensions/llm/credential_sources.rb', line 88 def socket_open?(host, port, timeout: 0.1) require 'socket' addr = Socket.sockaddr_in(port, host) sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) begin sock.connect_nonblock(addr) rescue IO::WaitWritable return false unless sock.wait_writable(timeout) begin sock.connect_nonblock(addr) rescue Errno::EISCONN # already connected — success end end true rescue StandardError false ensure sock&.close end |