lex-github 0.3.2 → 0.3.3

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: 8ea69decf68b91f650186b1719a7e29646ce4f7308bd2d90eebeadcd6048718d
4
- data.tar.gz: 3bdf52e0d20f137e7efb2f7c7330905de6b924d184edb4007c616435d679f44a
3
+ metadata.gz: 375eae72829e4b18ecd5162a92eecb0a93f22552e51bdedee79866d46d720fdd
4
+ data.tar.gz: 543b65111d082eec0abfa3a2e1c9d44e7e248d5ded04a6664f26f1f5d1025b00
5
5
  SHA512:
6
- metadata.gz: 2cc5f719ed57a84ebc740a0009a0836fecfcfaa162794e64806e9770dc653e2368ac37d44ef3655d29bd132ed1957148be691dd4544fadb05e969250949144ce
7
- data.tar.gz: 9bccdb81c9b7f52348c623b19473db41bc36f3d5539680789eceb7d50997136abafe3d42dcccb3010dacb57db8fb7c53998b1ea0374adfedab54e0414b7364af
6
+ metadata.gz: 7d3f74a3a0bd859ca3c8a35afca74a6c45afb7d057f2aff06fa2f6842b77a94ca5afaa840c08d323a1172f1385d3db6f9f34fb5b886a6deae5cfe98f5b45f139
7
+ data.tar.gz: 4c19e0bbae310e0987bbda49565a525c5cb6bf90abb4a91c81134834d4a53f671160e4767b6ec20862a68f8d680ddf4083be635e079752d7e217a5f5b353db34
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.3.3] - 2026-03-31
6
+
7
+ ### Fixed
8
+ - CLI runner output: `status` and `login` commands now print JSON results to stdout
9
+ - CLI runner errors print to stderr via `warn`
10
+
5
11
  ## [0.3.2] - 2026-03-31
6
12
 
7
13
  ### Added
@@ -15,9 +15,9 @@ module Legion
15
15
  csec = client_secret || settings_client_secret
16
16
  sc = scopes || settings_scopes
17
17
 
18
- unless cid && csec
18
+ unless cid
19
19
  return { error: 'missing_config',
20
- description: 'Set github.oauth.client_id or github.app.client_id and github.app.client_secret in settings or pass as arguments' }
20
+ description: 'Set github.oauth.client_id or github.app.client_id in settings' }
21
21
  end
22
22
 
23
23
  browser = Helpers::BrowserAuth.new(client_id: cid, client_secret: csec, scopes: sc)
@@ -1,90 +1,101 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'legion/extensions/github/cli/auth'
4
- require 'legion/extensions/github/cli/app'
5
- require 'legion/extensions/github/app/runners/credential_store'
3
+ require 'json'
4
+ require 'net/http'
5
+ require 'uri'
6
+ require 'rbconfig'
6
7
 
7
8
  module Legion
8
9
  module Extensions
9
10
  module Github
10
11
  module CLI
11
- class AuthRunner
12
- include Legion::Logging::Helper if defined?(Legion::Logging::Helper)
13
- include Github::CLI::Auth
14
- include Github::App::Runners::CredentialStore
15
-
16
- def credential_fingerprint(auth_type:, identifier:)
17
- "#{auth_type}:#{identifier}"
18
- end
12
+ DAEMON_URL = ENV.fetch('LEGION_API_URL', 'http://127.0.0.1:4567')
19
13
 
20
- def vault_get(path)
21
- return nil unless defined?(Legion::Crypt)
14
+ module DaemonApi
15
+ private
22
16
 
23
- ::Legion::Crypt.get(path)
24
- rescue StandardError => e
25
- log.warn("[lex-github] vault_get failed: #{e.message}")
26
- nil
17
+ def api_post(path, body = {})
18
+ uri = URI("#{DAEMON_URL}#{path}")
19
+ http = Net::HTTP.new(uri.host, uri.port)
20
+ http.open_timeout = 5
21
+ http.read_timeout = 30
22
+ request = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
23
+ request.body = ::JSON.generate(body)
24
+ parse_response(http.request(request))
25
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET => _e
26
+ { error: 'daemon_unavailable', description: "Legion daemon not running at #{DAEMON_URL}. Start it with: legionio start" }
27
27
  end
28
28
 
29
- def cache_connected?
30
- defined?(Legion::Cache) && ::Legion::Cache.connected?
31
- rescue StandardError => e
32
- log.debug("[lex-github] cache_connected? check failed: #{e.message}")
33
- false
29
+ def api_get(path)
30
+ uri = URI("#{DAEMON_URL}#{path}")
31
+ parse_response(Net::HTTP.get_response(uri))
32
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET => _e
33
+ { error: 'daemon_unavailable', description: "Legion daemon not running at #{DAEMON_URL}. Start it with: legionio start" }
34
34
  end
35
35
 
36
- def local_cache_connected?
37
- defined?(Legion::Cache::Local) && ::Legion::Cache::Local.connected?
38
- rescue StandardError => e
39
- log.debug("[lex-github] local_cache_connected? check failed: #{e.message}")
40
- false
36
+ def parse_response(response)
37
+ ::JSON.parse(response.body, symbolize_names: true)
38
+ rescue ::JSON::ParserError => _e
39
+ { error: "http_#{response.code}", description: response.body&.strip }
41
40
  end
42
41
 
43
- def cache_get(key)
44
- ::Legion::Cache.get(key)
45
- rescue StandardError => e
46
- log.debug("[lex-github] cache_get failed: #{e.message}")
47
- nil
42
+ def print_json(result)
43
+ if result.is_a?(Hash) && result[:error]
44
+ warn "Error: #{result[:error]}"
45
+ warn " #{result[:description]}" if result[:description]
46
+ else
47
+ puts ::JSON.pretty_generate(result)
48
+ end
48
49
  end
49
50
 
50
- def local_cache_get(key)
51
- ::Legion::Cache::Local.get(key)
52
- rescue StandardError => e
53
- log.debug("[lex-github] local_cache_get failed: #{e.message}")
54
- nil
51
+ def open_browser(url)
52
+ cmd = case RbConfig::CONFIG['host_os']
53
+ when /darwin/ then 'open'
54
+ when /linux/ then 'xdg-open'
55
+ when /mswin|mingw/ then 'start'
56
+ end
57
+ system(cmd, url) if cmd
55
58
  end
59
+ end
56
60
 
57
- def cache_set(key, value, ttl: 300)
58
- ::Legion::Cache.set(key, value, ttl)
59
- rescue StandardError => e
60
- log.debug("[lex-github] cache_set failed: #{e.message}")
61
- nil
61
+ class AuthRunner
62
+ include DaemonApi
63
+
64
+ def status
65
+ print_json(api_post('/api/extensions/github/runners/auth/status'))
62
66
  end
63
67
 
64
- def local_cache_set(key, value, ttl: 300)
65
- ::Legion::Cache::Local.set(key, value, ttl)
66
- rescue StandardError => e
67
- log.debug("[lex-github] local_cache_set failed: #{e.message}")
68
- nil
68
+ def login
69
+ print_json(api_post('/api/extensions/github/runners/auth/login'))
69
70
  end
70
71
  end
71
72
 
72
73
  class AppRunner
73
- include Legion::Logging::Helper if defined?(Legion::Logging::Helper)
74
- include Github::CLI::App
75
- include Github::App::Runners::CredentialStore
74
+ include DaemonApi
76
75
 
77
- def credential_fingerprint(auth_type:, identifier:)
78
- "#{auth_type}:#{identifier}"
79
- end
76
+ def setup
77
+ result = api_post('/api/extensions/github/cli/app/setup')
80
78
 
81
- def vault_get(path)
82
- return nil unless defined?(Legion::Crypt)
79
+ if result[:error]
80
+ print_json(result)
81
+ return
82
+ end
83
+
84
+ url = result.dig(:data, :manifest_url)
85
+ if url
86
+ warn 'Opening browser to create GitHub App...'
87
+ open_browser(url)
88
+ warn 'Waiting for callback...'
89
+ poll = api_post('/api/extensions/github/cli/app/await_callback',
90
+ { timeout: 300 })
91
+ print_json(poll)
92
+ else
93
+ print_json(result)
94
+ end
95
+ end
83
96
 
84
- ::Legion::Crypt.get(path)
85
- rescue StandardError => e
86
- log.warn("[lex-github] vault_get failed: #{e.message}")
87
- nil
97
+ def complete_setup
98
+ print_json(api_post('/api/extensions/github/cli/app/complete_setup'))
88
99
  end
89
100
  end
90
101
  end
@@ -14,7 +14,7 @@ module Legion
14
14
 
15
15
  attr_reader :client_id, :client_secret, :scopes
16
16
 
17
- def initialize(client_id:, client_secret:, scopes: DEFAULT_SCOPES, auth: nil, **)
17
+ def initialize(client_id:, client_secret: nil, scopes: DEFAULT_SCOPES, auth: nil, **)
18
18
  @client_id = client_id
19
19
  @client_secret = client_secret
20
20
  @scopes = scopes
@@ -33,21 +33,19 @@ module Legion
33
33
  { result: "https://github.com/login/oauth/authorize?#{params}" }
34
34
  end
35
35
 
36
- def exchange_code(client_id:, client_secret:, code:, redirect_uri:, code_verifier:, **)
37
- response = oauth_connection.post('/login/oauth/access_token', {
38
- client_id: client_id, client_secret: client_secret,
39
- code: code, redirect_uri: redirect_uri,
40
- code_verifier: code_verifier
41
- })
36
+ def exchange_code(client_id:, code:, redirect_uri:, code_verifier:, client_secret: nil, **)
37
+ body = { client_id: client_id, code: code,
38
+ redirect_uri: redirect_uri, code_verifier: code_verifier }
39
+ body[:client_secret] = client_secret if client_secret
40
+ response = oauth_connection.post('/login/oauth/access_token', body)
42
41
  { result: response.body }
43
42
  end
44
43
 
45
- def refresh_token(client_id:, client_secret:, refresh_token:, **)
46
- response = oauth_connection.post('/login/oauth/access_token', {
47
- client_id: client_id, client_secret: client_secret,
48
- refresh_token: refresh_token,
49
- grant_type: 'refresh_token'
50
- })
44
+ def refresh_token(client_id:, refresh_token:, client_secret: nil, **)
45
+ body = { client_id: client_id, refresh_token: refresh_token,
46
+ grant_type: 'refresh_token' }
47
+ body[:client_secret] = client_secret if client_secret
48
+ response = oauth_connection.post('/login/oauth/access_token', body)
51
49
  { result: response.body }
52
50
  end
53
51
 
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/github/helpers/client'
4
+ require 'legion/extensions/github/helpers/browser_auth'
5
+ require 'legion/extensions/github/app/runners/auth'
6
+ require 'legion/extensions/github/app/runners/credential_store'
7
+ require 'legion/extensions/github/oauth/runners/auth'
8
+
9
+ module Legion
10
+ module Extensions
11
+ module Github
12
+ module Runners
13
+ module Auth
14
+ include Legion::Extensions::Github::Helpers::Client
15
+ include Legion::Extensions::Github::App::Runners::Auth
16
+ include Legion::Extensions::Github::App::Runners::CredentialStore
17
+ include Legion::Extensions::Github::OAuth::Runners::Auth
18
+
19
+ def self.remote_invocable?
20
+ false
21
+ end
22
+
23
+ def status(**)
24
+ cred = resolve_credential
25
+ unless cred
26
+ log.warn('[lex-github] auth status: no credential found across all sources')
27
+ return { result: { authenticated: false } }
28
+ end
29
+
30
+ log.info("[lex-github] auth status: credential found via #{cred[:auth_type]}")
31
+
32
+ user_info = {}
33
+ scopes = nil
34
+ begin
35
+ response = connection(token: cred[:token]).get('/user')
36
+ user_info = response.body || {}
37
+ headers = response.respond_to?(:headers) ? response.headers : {}
38
+ scopes_header = headers['X-OAuth-Scopes'] || headers['x-oauth-scopes']
39
+ scopes = scopes_header&.split(',')&.map(&:strip)
40
+ log.info("[lex-github] auth status: authenticated as #{user_info['login']} (#{cred[:auth_type]})")
41
+ rescue StandardError => e
42
+ log.warn("[lex-github] auth status: credential found but /user request failed: #{e.message}")
43
+ end
44
+
45
+ { result: { authenticated: true, auth_type: cred[:auth_type],
46
+ user: user_info['login'], scopes: scopes } }
47
+ end
48
+
49
+ def login(client_id: nil, scopes: nil, **)
50
+ cid = client_id || settings_client_id
51
+ unless cid
52
+ log.error('[lex-github] auth login: no client_id configured — set github.app.client_id in settings')
53
+ return { error: 'missing_config', description: 'Set github.app.client_id in settings' }
54
+ end
55
+
56
+ log.info("[lex-github] auth login: starting OAuth flow with client_id=#{cid[0..7]}...")
57
+
58
+ sc = scopes || settings_scopes
59
+ browser = Helpers::BrowserAuth.new(client_id: cid, scopes: sc)
60
+ result = browser.authenticate
61
+
62
+ if result[:error]
63
+ log.error("[lex-github] auth login failed: #{result[:error]} — #{result[:description]}")
64
+ return { result: nil, error: result[:error], description: result[:description] }
65
+ end
66
+
67
+ if result[:result]&.dig('access_token')
68
+ user = begin
69
+ current_user(token: result[:result]['access_token'])
70
+ rescue StandardError => e
71
+ log.warn("[lex-github] auth login: token obtained but /user lookup failed: #{e.message}")
72
+ 'default'
73
+ end
74
+
75
+ log.info("[lex-github] auth login: authenticated as #{user}")
76
+
77
+ if respond_to?(:store_oauth_token, true)
78
+ store_oauth_token(
79
+ user: user,
80
+ access_token: result[:result]['access_token'],
81
+ refresh_token: result[:result]['refresh_token'],
82
+ expires_in: result[:result]['expires_in']
83
+ )
84
+ log.info("[lex-github] auth login: token stored for user=#{user}")
85
+ else
86
+ log.warn('[lex-github] auth login: store_oauth_token not available — token not persisted')
87
+ end
88
+ else
89
+ log.warn('[lex-github] auth login: OAuth completed but no access_token in response')
90
+ end
91
+
92
+ result
93
+ end
94
+
95
+ def installations(**)
96
+ log.info('[lex-github] listing app installations')
97
+ list_installations(**)
98
+ end
99
+
100
+ private
101
+
102
+ def current_user(token:)
103
+ connection(token: token).get('/user').body['login']
104
+ end
105
+
106
+ def settings_client_id
107
+ defined?(Legion::Settings) &&
108
+ (Legion::Settings.dig(:github, :oauth, :client_id) ||
109
+ Legion::Settings.dig(:github, :app, :client_id))
110
+ rescue StandardError => _e
111
+ nil
112
+ end
113
+
114
+ def settings_scopes
115
+ defined?(Legion::Settings) && Legion::Settings.dig(:github, :oauth, :scopes)
116
+ rescue StandardError => _e
117
+ nil
118
+ end
119
+
120
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
121
+ Legion::Extensions::Helpers.const_defined?(:Lex, false)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Github
6
- VERSION = '0.3.2'
6
+ VERSION = '0.3.3'
7
7
  end
8
8
  end
9
9
  end
@@ -35,6 +35,7 @@ require 'legion/extensions/github/runners/actions'
35
35
  require 'legion/extensions/github/runners/checks'
36
36
  require 'legion/extensions/github/runners/releases'
37
37
  require 'legion/extensions/github/runners/deployments'
38
+ require 'legion/extensions/github/runners/auth'
38
39
  require 'legion/extensions/github/runners/repository_webhooks'
39
40
  require 'legion/extensions/github/client'
40
41
  require 'legion/extensions/github/cli/runner'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-github
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -203,6 +203,7 @@ files:
203
203
  - lib/legion/extensions/github/oauth/transport/exchanges/oauth.rb
204
204
  - lib/legion/extensions/github/oauth/transport/queues/auth.rb
205
205
  - lib/legion/extensions/github/runners/actions.rb
206
+ - lib/legion/extensions/github/runners/auth.rb
206
207
  - lib/legion/extensions/github/runners/branches.rb
207
208
  - lib/legion/extensions/github/runners/checks.rb
208
209
  - lib/legion/extensions/github/runners/comments.rb