lex-github 0.3.1 → 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 +4 -4
- data/CHANGELOG.md +14 -0
- data/lib/legion/extensions/github/cli/auth.rb +2 -2
- data/lib/legion/extensions/github/cli/runner.rb +104 -0
- data/lib/legion/extensions/github/helpers/browser_auth.rb +1 -1
- data/lib/legion/extensions/github/oauth/runners/auth.rb +10 -12
- data/lib/legion/extensions/github/runners/auth.rb +126 -0
- data/lib/legion/extensions/github/version.rb +1 -1
- data/lib/legion/extensions/github.rb +38 -0
- data/lib/lex/github.rb +4 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 375eae72829e4b18ecd5162a92eecb0a93f22552e51bdedee79866d46d720fdd
|
|
4
|
+
data.tar.gz: 543b65111d082eec0abfa3a2e1c9d44e7e248d5ded04a6664f26f1f5d1025b00
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7d3f74a3a0bd859ca3c8a35afca74a6c45afb7d057f2aff06fa2f6842b77a94ca5afaa840c08d323a1172f1385d3db6f9f34fb5b886a6deae5cfe98f5b45f139
|
|
7
|
+
data.tar.gz: 4c19e0bbae310e0987bbda49565a525c5cb6bf90abb4a91c81134834d4a53f671160e4767b6ec20862a68f8d680ddf4083be635e079752d7e217a5f5b353db34
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
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
|
+
|
|
11
|
+
## [0.3.2] - 2026-03-31
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- CLI command registration: `legionio lex exec github auth status|login` and `legionio lex exec github app setup|complete_setup`
|
|
15
|
+
- `CLI::AuthRunner` and `CLI::AppRunner` wrapper classes for `lex exec` dispatch
|
|
16
|
+
- Self-registering CLI manifest at `~/.legionio/cache/cli/lex-github.json` (written on first require)
|
|
17
|
+
- Require redirect `lib/lex/github.rb` for `lex exec` compatibility
|
|
18
|
+
|
|
5
19
|
## [0.3.1] - 2026-03-30
|
|
6
20
|
|
|
7
21
|
### Changed
|
|
@@ -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
|
|
18
|
+
unless cid
|
|
19
19
|
return { error: 'missing_config',
|
|
20
|
-
description: 'Set github.oauth.client_id or github.app.client_id
|
|
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)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'uri'
|
|
6
|
+
require 'rbconfig'
|
|
7
|
+
|
|
8
|
+
module Legion
|
|
9
|
+
module Extensions
|
|
10
|
+
module Github
|
|
11
|
+
module CLI
|
|
12
|
+
DAEMON_URL = ENV.fetch('LEGION_API_URL', 'http://127.0.0.1:4567')
|
|
13
|
+
|
|
14
|
+
module DaemonApi
|
|
15
|
+
private
|
|
16
|
+
|
|
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
|
+
end
|
|
28
|
+
|
|
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
|
+
end
|
|
35
|
+
|
|
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 }
|
|
40
|
+
end
|
|
41
|
+
|
|
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
|
|
49
|
+
end
|
|
50
|
+
|
|
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
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class AuthRunner
|
|
62
|
+
include DaemonApi
|
|
63
|
+
|
|
64
|
+
def status
|
|
65
|
+
print_json(api_post('/api/extensions/github/runners/auth/status'))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def login
|
|
69
|
+
print_json(api_post('/api/extensions/github/runners/auth/login'))
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class AppRunner
|
|
74
|
+
include DaemonApi
|
|
75
|
+
|
|
76
|
+
def setup
|
|
77
|
+
result = api_post('/api/extensions/github/cli/app/setup')
|
|
78
|
+
|
|
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
|
|
96
|
+
|
|
97
|
+
def complete_setup
|
|
98
|
+
print_json(api_post('/api/extensions/github/cli/app/complete_setup'))
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
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
|
|
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:,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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:,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
@@ -35,13 +35,51 @@ 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'
|
|
41
|
+
require 'legion/extensions/github/cli/runner'
|
|
40
42
|
|
|
41
43
|
module Legion
|
|
42
44
|
module Extensions
|
|
43
45
|
module Github
|
|
44
46
|
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core, false
|
|
47
|
+
|
|
48
|
+
CLI_COMMANDS = {
|
|
49
|
+
'auth' => {
|
|
50
|
+
class_name: 'Legion::Extensions::Github::CLI::AuthRunner',
|
|
51
|
+
methods: {
|
|
52
|
+
'login' => { desc: 'Authenticate with GitHub via OAuth browser flow', args: '' },
|
|
53
|
+
'status' => { desc: 'Show current GitHub authentication status', args: '' }
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
'app' => {
|
|
57
|
+
class_name: 'Legion::Extensions::Github::CLI::AppRunner',
|
|
58
|
+
methods: {
|
|
59
|
+
'setup' => { desc: 'Create a new GitHub App via manifest flow', args: '' },
|
|
60
|
+
'complete_setup' => { desc: 'Complete GitHub App setup with authorization code', args: '' }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}.freeze
|
|
64
|
+
|
|
65
|
+
begin
|
|
66
|
+
manifest_dir = ::File.expand_path('~/.legionio/cache/cli')
|
|
67
|
+
manifest_path = ::File.join(manifest_dir, 'lex-github.json')
|
|
68
|
+
unless ::File.exist?(manifest_path) && ::File.read(manifest_path).include?(VERSION)
|
|
69
|
+
require 'fileutils'
|
|
70
|
+
::FileUtils.mkdir_p(manifest_dir)
|
|
71
|
+
serialized = CLI_COMMANDS.transform_values do |cmd|
|
|
72
|
+
{ 'class' => cmd[:class_name],
|
|
73
|
+
'methods' => cmd[:methods].transform_values { |m| { 'desc' => m[:desc], 'args' => m[:args] } } }
|
|
74
|
+
end
|
|
75
|
+
::File.write(manifest_path, ::JSON.pretty_generate(
|
|
76
|
+
'gem' => 'lex-github', 'version' => VERSION,
|
|
77
|
+
'alias' => 'github', 'commands' => serialized
|
|
78
|
+
))
|
|
79
|
+
end
|
|
80
|
+
rescue StandardError => e
|
|
81
|
+
warn "[lex-github] CLI manifest write skipped: #{e.message}" if ENV['LEGION_DEBUG']
|
|
82
|
+
end
|
|
45
83
|
end
|
|
46
84
|
end
|
|
47
85
|
end
|
data/lib/lex/github.rb
ADDED
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.
|
|
4
|
+
version: 0.3.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -185,6 +185,7 @@ files:
|
|
|
185
185
|
- lib/legion/extensions/github/app/transport/queues/webhooks.rb
|
|
186
186
|
- lib/legion/extensions/github/cli/app.rb
|
|
187
187
|
- lib/legion/extensions/github/cli/auth.rb
|
|
188
|
+
- lib/legion/extensions/github/cli/runner.rb
|
|
188
189
|
- lib/legion/extensions/github/client.rb
|
|
189
190
|
- lib/legion/extensions/github/errors.rb
|
|
190
191
|
- lib/legion/extensions/github/helpers/browser_auth.rb
|
|
@@ -202,6 +203,7 @@ files:
|
|
|
202
203
|
- lib/legion/extensions/github/oauth/transport/exchanges/oauth.rb
|
|
203
204
|
- lib/legion/extensions/github/oauth/transport/queues/auth.rb
|
|
204
205
|
- lib/legion/extensions/github/runners/actions.rb
|
|
206
|
+
- lib/legion/extensions/github/runners/auth.rb
|
|
205
207
|
- lib/legion/extensions/github/runners/branches.rb
|
|
206
208
|
- lib/legion/extensions/github/runners/checks.rb
|
|
207
209
|
- lib/legion/extensions/github/runners/comments.rb
|
|
@@ -219,6 +221,7 @@ files:
|
|
|
219
221
|
- lib/legion/extensions/github/runners/search.rb
|
|
220
222
|
- lib/legion/extensions/github/runners/users.rb
|
|
221
223
|
- lib/legion/extensions/github/version.rb
|
|
224
|
+
- lib/lex/github.rb
|
|
222
225
|
homepage: https://github.com/LegionIO/lex-github
|
|
223
226
|
licenses:
|
|
224
227
|
- MIT
|