legionio 1.6.44 → 1.6.45
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 +12 -0
- data/lib/legion/api/helpers.rb +24 -0
- data/lib/legion/api/knowledge.rb +146 -0
- data/lib/legion/api.rb +2 -0
- data/lib/legion/cli/absorb_command.rb +3 -53
- data/lib/legion/cli/api_client.rb +107 -0
- data/lib/legion/cli/chat/tools/search_traces.rb +1 -1
- data/lib/legion/cli/codegen_command.rb +30 -60
- data/lib/legion/cli/knowledge_command.rb +35 -114
- data/lib/legion/cli/schedule_command.rb +61 -103
- data/lib/legion/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 55adb4b5b957ffa41629f5d0cf48961096230993a9654f1f85d956a845773043
|
|
4
|
+
data.tar.gz: 41d97316c383dae8178b9730d2a3712d1669870180e01d55a966fbc3e79afbbc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5d062ded2d5c0a87aa28864abd1c74a3d8aacf075f43fba8bd6267a5adcfeb0e74498df3583b5713921901cfbbd0ec96f57986a71dbeb70c48649541404dd484
|
|
7
|
+
data.tar.gz: afa0809aa907d8e9e508f257d20dd05db8e4c0ddad7dd9aeb989ec6cf135c4df48fcce0a35dda6bfbceb2ba7d1a4d6ad1a658342b026d044bb3262b2ed1d110c
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.6.45] - 2026-03-31
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `Legion::CLI::ApiClient` shared module — extracts api_get/api_post/api_put/api_delete helpers into a reusable mixin for all CLI commands that talk to the daemon API
|
|
9
|
+
- `/api/knowledge/*` API routes — query, retrieve, ingest, status, health, maintain, quality, and monitor CRUD endpoints for lex-knowledge
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- `legionio knowledge` commands now route through the local API instead of loading extension classes directly (fixes NameError when daemon not running)
|
|
13
|
+
- `legionio schedule` commands now route through the existing `/api/schedules/*` API instead of querying Sequel models directly
|
|
14
|
+
- `legionio codegen` commands now route through the existing `/api/codegen/*` API instead of checking `defined?` guards that always fail in CLI context
|
|
15
|
+
- `legionio absorb` commands now use the shared `ApiClient` module instead of inline HTTP helpers
|
|
16
|
+
|
|
5
17
|
## [1.6.44] - 2026-03-31
|
|
6
18
|
|
|
7
19
|
### Added
|
data/lib/legion/api/helpers.rb
CHANGED
|
@@ -52,6 +52,30 @@ module Legion
|
|
|
52
52
|
halt 503, json_error('scheduler_unavailable', 'lex-scheduler is not loaded', status_code: 503)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
def require_knowledge_query!
|
|
56
|
+
return if defined?(Legion::Extensions::Knowledge::Runners::Query)
|
|
57
|
+
|
|
58
|
+
halt 503, json_error('knowledge_unavailable', 'lex-knowledge is not loaded', status_code: 503)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def require_knowledge_ingest!
|
|
62
|
+
return if defined?(Legion::Extensions::Knowledge::Runners::Ingest)
|
|
63
|
+
|
|
64
|
+
halt 503, json_error('knowledge_unavailable', 'lex-knowledge is not loaded', status_code: 503)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def require_knowledge_maintenance!
|
|
68
|
+
return if defined?(Legion::Extensions::Knowledge::Runners::Maintenance)
|
|
69
|
+
|
|
70
|
+
halt 503, json_error('knowledge_unavailable', 'lex-knowledge is not loaded', status_code: 503)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def require_knowledge_monitor!
|
|
74
|
+
return if defined?(Legion::Extensions::Knowledge::Runners::Monitor)
|
|
75
|
+
|
|
76
|
+
halt 503, json_error('knowledge_unavailable', 'lex-knowledge is not loaded', status_code: 503)
|
|
77
|
+
end
|
|
78
|
+
|
|
55
79
|
def require_trace_search!
|
|
56
80
|
return if defined?(Legion::TraceSearch) && defined?(Legion::LLM)
|
|
57
81
|
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
class API < Sinatra::Base
|
|
5
|
+
module Routes
|
|
6
|
+
module Knowledge
|
|
7
|
+
def self.registered(app)
|
|
8
|
+
register_query_routes(app)
|
|
9
|
+
register_ingest_routes(app)
|
|
10
|
+
register_maintenance_routes(app)
|
|
11
|
+
register_monitor_routes(app)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.register_query_routes(app)
|
|
15
|
+
app.post '/api/knowledge/query' do
|
|
16
|
+
require_knowledge_query!
|
|
17
|
+
body = parse_request_body
|
|
18
|
+
result = Legion::Extensions::Knowledge::Runners::Query.query(
|
|
19
|
+
question: body[:question],
|
|
20
|
+
top_k: body[:top_k] || 5,
|
|
21
|
+
synthesize: body.fetch(:synthesize, true)
|
|
22
|
+
)
|
|
23
|
+
json_response(result)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
app.post '/api/knowledge/retrieve' do
|
|
27
|
+
require_knowledge_query!
|
|
28
|
+
body = parse_request_body
|
|
29
|
+
result = Legion::Extensions::Knowledge::Runners::Query.retrieve(
|
|
30
|
+
question: body[:question],
|
|
31
|
+
top_k: body[:top_k] || 5
|
|
32
|
+
)
|
|
33
|
+
json_response(result)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.register_ingest_routes(app)
|
|
38
|
+
app.post '/api/knowledge/ingest' do
|
|
39
|
+
require_knowledge_ingest!
|
|
40
|
+
body = parse_request_body
|
|
41
|
+
|
|
42
|
+
result = if body[:content]
|
|
43
|
+
Legion::Extensions::Knowledge::Runners::Ingest.ingest_file(
|
|
44
|
+
content: body[:content],
|
|
45
|
+
tags: body[:tags] || [],
|
|
46
|
+
source: body[:source]
|
|
47
|
+
)
|
|
48
|
+
elsif body[:path]
|
|
49
|
+
if File.directory?(body[:path])
|
|
50
|
+
Legion::Extensions::Knowledge::Runners::Ingest.ingest_corpus(
|
|
51
|
+
path: body[:path],
|
|
52
|
+
force: body[:force] || false,
|
|
53
|
+
dry_run: body[:dry_run] || false
|
|
54
|
+
)
|
|
55
|
+
else
|
|
56
|
+
Legion::Extensions::Knowledge::Runners::Ingest.ingest_file(
|
|
57
|
+
file_path: body[:path],
|
|
58
|
+
force: body[:force] || false,
|
|
59
|
+
dry_run: body[:dry_run] || false
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
else
|
|
63
|
+
halt 400, json_error('missing_param', 'content or path is required')
|
|
64
|
+
end
|
|
65
|
+
json_response(result)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
app.post '/api/knowledge/status' do
|
|
69
|
+
require_knowledge_ingest!
|
|
70
|
+
body = parse_request_body
|
|
71
|
+
path = body[:path] || Dir.pwd
|
|
72
|
+
result = Legion::Extensions::Knowledge::Runners::Ingest.scan_corpus(path: path)
|
|
73
|
+
json_response(result)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.register_maintenance_routes(app)
|
|
78
|
+
app.post '/api/knowledge/health' do
|
|
79
|
+
require_knowledge_maintenance!
|
|
80
|
+
body = parse_request_body
|
|
81
|
+
result = Legion::Extensions::Knowledge::Runners::Maintenance.health(path: body[:path])
|
|
82
|
+
json_response(result)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
app.post '/api/knowledge/maintain' do
|
|
86
|
+
require_knowledge_maintenance!
|
|
87
|
+
body = parse_request_body
|
|
88
|
+
result = Legion::Extensions::Knowledge::Runners::Maintenance.cleanup_orphans(
|
|
89
|
+
path: body[:path],
|
|
90
|
+
dry_run: body.fetch(:dry_run, true)
|
|
91
|
+
)
|
|
92
|
+
json_response(result)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
app.post '/api/knowledge/quality' do
|
|
96
|
+
require_knowledge_maintenance!
|
|
97
|
+
body = parse_request_body
|
|
98
|
+
result = Legion::Extensions::Knowledge::Runners::Maintenance.quality_report(
|
|
99
|
+
limit: body[:limit] || 10
|
|
100
|
+
)
|
|
101
|
+
json_response(result)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.register_monitor_routes(app)
|
|
106
|
+
app.get '/api/knowledge/monitors' do
|
|
107
|
+
require_knowledge_monitor!
|
|
108
|
+
monitors = Legion::Extensions::Knowledge::Runners::Monitor.list_monitors
|
|
109
|
+
json_response(monitors)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
app.post '/api/knowledge/monitors' do
|
|
113
|
+
require_knowledge_monitor!
|
|
114
|
+
body = parse_request_body
|
|
115
|
+
result = Legion::Extensions::Knowledge::Runners::Monitor.add_monitor(
|
|
116
|
+
path: body[:path],
|
|
117
|
+
extensions: body[:extensions],
|
|
118
|
+
label: body[:label]
|
|
119
|
+
)
|
|
120
|
+
json_response(result, status_code: 201)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
app.delete '/api/knowledge/monitors' do
|
|
124
|
+
require_knowledge_monitor!
|
|
125
|
+
body = parse_request_body
|
|
126
|
+
result = Legion::Extensions::Knowledge::Runners::Monitor.remove_monitor(
|
|
127
|
+
identifier: body[:identifier]
|
|
128
|
+
)
|
|
129
|
+
json_response(result)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
app.get '/api/knowledge/monitors/status' do
|
|
133
|
+
require_knowledge_monitor!
|
|
134
|
+
result = Legion::Extensions::Knowledge::Runners::Monitor.monitor_status
|
|
135
|
+
json_response(result)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
class << self
|
|
140
|
+
private :register_query_routes, :register_ingest_routes,
|
|
141
|
+
:register_maintenance_routes, :register_monitor_routes
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
data/lib/legion/api.rb
CHANGED
|
@@ -47,6 +47,7 @@ require_relative 'api/traces'
|
|
|
47
47
|
require_relative 'api/stats'
|
|
48
48
|
require_relative 'api/absorbers'
|
|
49
49
|
require_relative 'api/codegen'
|
|
50
|
+
require_relative 'api/knowledge'
|
|
50
51
|
require_relative 'api/logs'
|
|
51
52
|
require_relative 'api/router'
|
|
52
53
|
require_relative 'api/library_routes'
|
|
@@ -176,6 +177,7 @@ module Legion
|
|
|
176
177
|
register Routes::Stats
|
|
177
178
|
register Routes::Absorbers
|
|
178
179
|
register Routes::Codegen
|
|
180
|
+
register Routes::Knowledge
|
|
179
181
|
register Routes::Logs
|
|
180
182
|
register Routes::TbiPatterns
|
|
181
183
|
register Routes::GraphQL if defined?(Routes::GraphQL)
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require 'uri'
|
|
5
|
-
require 'json'
|
|
3
|
+
require_relative 'api_client'
|
|
6
4
|
|
|
7
5
|
module Legion
|
|
8
6
|
module CLI
|
|
@@ -66,60 +64,12 @@ module Legion
|
|
|
66
64
|
end
|
|
67
65
|
|
|
68
66
|
no_commands do
|
|
67
|
+
include ApiClient
|
|
68
|
+
|
|
69
69
|
def formatter
|
|
70
70
|
@formatter ||= Output::Formatter.new(json: options[:json], color: !options[:no_color])
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
-
def api_port
|
|
74
|
-
Connection.ensure_settings
|
|
75
|
-
api_settings = Legion::Settings[:api]
|
|
76
|
-
(api_settings.is_a?(Hash) && api_settings[:port]) || 4567
|
|
77
|
-
rescue StandardError
|
|
78
|
-
4567
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def api_post(path, **payload)
|
|
82
|
-
uri = URI("http://127.0.0.1:#{api_port}#{path}")
|
|
83
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
84
|
-
http.read_timeout = 300
|
|
85
|
-
request = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
|
|
86
|
-
request.body = ::JSON.generate(payload)
|
|
87
|
-
response = http.request(request)
|
|
88
|
-
unless response.is_a?(Net::HTTPSuccess)
|
|
89
|
-
formatter.error("API returned #{response.code} for #{path}")
|
|
90
|
-
raise SystemExit, 1
|
|
91
|
-
end
|
|
92
|
-
body = ::JSON.parse(response.body, symbolize_names: true)
|
|
93
|
-
body[:data]
|
|
94
|
-
rescue Errno::ECONNREFUSED
|
|
95
|
-
formatter.error('Daemon not running. Start with: legionio start')
|
|
96
|
-
raise SystemExit, 1
|
|
97
|
-
rescue SystemExit
|
|
98
|
-
raise
|
|
99
|
-
rescue StandardError => e
|
|
100
|
-
formatter.error("API request failed: #{e.message}")
|
|
101
|
-
raise SystemExit, 1
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def api_get(path)
|
|
105
|
-
uri = URI("http://127.0.0.1:#{api_port}#{path}")
|
|
106
|
-
response = Net::HTTP.get_response(uri)
|
|
107
|
-
unless response.is_a?(Net::HTTPSuccess)
|
|
108
|
-
formatter.error("API returned #{response.code} for #{path}")
|
|
109
|
-
raise SystemExit, 1
|
|
110
|
-
end
|
|
111
|
-
body = ::JSON.parse(response.body, symbolize_names: true)
|
|
112
|
-
body[:data]
|
|
113
|
-
rescue Errno::ECONNREFUSED
|
|
114
|
-
formatter.error('Daemon not running. Start with: legionio start')
|
|
115
|
-
raise SystemExit, 1
|
|
116
|
-
rescue SystemExit
|
|
117
|
-
raise
|
|
118
|
-
rescue StandardError => e
|
|
119
|
-
formatter.error("API request failed: #{e.message}")
|
|
120
|
-
raise SystemExit, 1
|
|
121
|
-
end
|
|
122
|
-
|
|
123
73
|
def fetch_absorbers
|
|
124
74
|
api_get('/api/absorbers')
|
|
125
75
|
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
module Legion
|
|
8
|
+
module CLI
|
|
9
|
+
# Shared HTTP client for CLI commands that talk to the running daemon API.
|
|
10
|
+
# Include this module inside a Thor command's `no_commands` block, or
|
|
11
|
+
# extend it at the class level, to get api_get / api_post / api_put /
|
|
12
|
+
# api_delete helpers that target http://127.0.0.1:<port>/api/*.
|
|
13
|
+
module ApiClient
|
|
14
|
+
def api_port
|
|
15
|
+
Connection.ensure_settings
|
|
16
|
+
api_settings = Legion::Settings[:api]
|
|
17
|
+
(api_settings.is_a?(Hash) && api_settings[:port]) || 4567
|
|
18
|
+
rescue StandardError
|
|
19
|
+
4567
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def api_get(path)
|
|
23
|
+
uri = URI("http://127.0.0.1:#{api_port}#{path}")
|
|
24
|
+
http = build_http(uri)
|
|
25
|
+
response = http.get(uri.request_uri)
|
|
26
|
+
handle_response(response, path)
|
|
27
|
+
rescue Errno::ECONNREFUSED
|
|
28
|
+
daemon_not_running!
|
|
29
|
+
rescue SystemExit
|
|
30
|
+
raise
|
|
31
|
+
rescue StandardError => e
|
|
32
|
+
api_error!(e, path)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def api_post(path, **payload)
|
|
36
|
+
uri = URI("http://127.0.0.1:#{api_port}#{path}")
|
|
37
|
+
http = build_http(uri, read_timeout: 300)
|
|
38
|
+
request = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
|
|
39
|
+
request.body = ::JSON.generate(payload)
|
|
40
|
+
response = http.request(request)
|
|
41
|
+
handle_response(response, path)
|
|
42
|
+
rescue Errno::ECONNREFUSED
|
|
43
|
+
daemon_not_running!
|
|
44
|
+
rescue SystemExit
|
|
45
|
+
raise
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
api_error!(e, path)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def api_put(path, **payload)
|
|
51
|
+
uri = URI("http://127.0.0.1:#{api_port}#{path}")
|
|
52
|
+
http = build_http(uri)
|
|
53
|
+
request = Net::HTTP::Put.new(uri.path, 'Content-Type' => 'application/json')
|
|
54
|
+
request.body = ::JSON.generate(payload)
|
|
55
|
+
response = http.request(request)
|
|
56
|
+
handle_response(response, path)
|
|
57
|
+
rescue Errno::ECONNREFUSED
|
|
58
|
+
daemon_not_running!
|
|
59
|
+
rescue SystemExit
|
|
60
|
+
raise
|
|
61
|
+
rescue StandardError => e
|
|
62
|
+
api_error!(e, path)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def api_delete(path)
|
|
66
|
+
uri = URI("http://127.0.0.1:#{api_port}#{path}")
|
|
67
|
+
http = build_http(uri)
|
|
68
|
+
response = http.delete(uri.path)
|
|
69
|
+
handle_response(response, path)
|
|
70
|
+
rescue Errno::ECONNREFUSED
|
|
71
|
+
daemon_not_running!
|
|
72
|
+
rescue SystemExit
|
|
73
|
+
raise
|
|
74
|
+
rescue StandardError => e
|
|
75
|
+
api_error!(e, path)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def build_http(uri, read_timeout: 10)
|
|
81
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
82
|
+
http.open_timeout = 3
|
|
83
|
+
http.read_timeout = read_timeout
|
|
84
|
+
http
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def handle_response(response, path)
|
|
88
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
89
|
+
formatter.error("API returned #{response.code} for #{path}")
|
|
90
|
+
raise SystemExit, 1
|
|
91
|
+
end
|
|
92
|
+
body = ::JSON.parse(response.body, symbolize_names: true)
|
|
93
|
+
body[:data]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def daemon_not_running!
|
|
97
|
+
formatter.error('Daemon not running. Start with: legionio start')
|
|
98
|
+
raise SystemExit, 1
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def api_error!(err, path)
|
|
102
|
+
formatter.error("API request failed (#{path}): #{err.message}")
|
|
103
|
+
raise SystemExit, 1
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -31,7 +31,7 @@ module Legion
|
|
|
31
31
|
['Job', 'jobTitle', :jobTitle]
|
|
32
32
|
].freeze
|
|
33
33
|
|
|
34
|
-
def execute(query:, person: nil, domain: nil, trace_type: nil, limit: nil)
|
|
34
|
+
def execute(query:, person: nil, domain: nil, trace_type: nil, limit: nil, **) # rubocop:disable Metrics/ParameterLists
|
|
35
35
|
return 'Memory trace system not available (lex-agentic-memory not loaded).' unless trace_store_available?
|
|
36
36
|
|
|
37
37
|
limit = (limit || 20).clamp(1, 50)
|
|
@@ -1,103 +1,73 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'api_client'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module CLI
|
|
5
7
|
class CodegenCommand < Thor
|
|
6
8
|
namespace :codegen
|
|
7
9
|
|
|
10
|
+
class_option :json, type: :boolean, default: false, desc: 'Output as JSON'
|
|
11
|
+
class_option :no_color, type: :boolean, default: false, desc: 'Disable color output'
|
|
12
|
+
|
|
8
13
|
desc 'status', 'Show codegen cycle stats, pending gaps, registry counts'
|
|
9
14
|
def status
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
say Legion::JSON.dump({ data: data })
|
|
13
|
-
else
|
|
14
|
-
say Legion::JSON.dump({ error: 'codegen not available' })
|
|
15
|
-
end
|
|
15
|
+
data = api_get('/api/codegen/status')
|
|
16
|
+
formatter.json(data)
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
desc 'list', 'List generated functions'
|
|
19
20
|
method_option :status, type: :string, desc: 'Filter by status'
|
|
20
21
|
def list
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
records = Legion::Extensions::Codegen::Helpers::GeneratedRegistry.list(status: options[:status])
|
|
27
|
-
say Legion::JSON.dump({ data: records })
|
|
22
|
+
path = '/api/codegen/generated'
|
|
23
|
+
path += "?status=#{options[:status]}" if options[:status]
|
|
24
|
+
data = api_get(path)
|
|
25
|
+
formatter.json(data)
|
|
28
26
|
end
|
|
29
27
|
|
|
30
28
|
desc 'show ID', 'Show details of a generated function'
|
|
31
29
|
def show(id)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
record = Legion::Extensions::Codegen::Helpers::GeneratedRegistry.get(id: id)
|
|
38
|
-
if record
|
|
39
|
-
say Legion::JSON.dump({ data: record })
|
|
40
|
-
else
|
|
41
|
-
say Legion::JSON.dump({ error: 'not found' })
|
|
42
|
-
end
|
|
30
|
+
data = api_get("/api/codegen/generated/#{id}")
|
|
31
|
+
formatter.json(data)
|
|
43
32
|
end
|
|
44
33
|
|
|
45
34
|
desc 'approve ID', 'Manually approve a parked generated function'
|
|
46
35
|
def approve(id)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
result = Legion::Extensions::Codegen::Runners::ReviewHandler.handle_verdict(
|
|
53
|
-
review: { generation_id: id, verdict: :approve, confidence: 1.0 }
|
|
54
|
-
)
|
|
55
|
-
say Legion::JSON.dump({ data: result })
|
|
36
|
+
data = api_post("/api/codegen/generated/#{id}/approve")
|
|
37
|
+
formatter.json(data)
|
|
56
38
|
end
|
|
57
39
|
|
|
58
40
|
desc 'reject ID', 'Manually reject a generated function'
|
|
59
41
|
def reject(id)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
Legion::Extensions::Codegen::Helpers::GeneratedRegistry.update_status(id: id, status: 'rejected')
|
|
66
|
-
say Legion::JSON.dump({ data: { id: id, status: 'rejected' } })
|
|
42
|
+
data = api_post("/api/codegen/generated/#{id}/reject")
|
|
43
|
+
formatter.json(data)
|
|
67
44
|
end
|
|
68
45
|
|
|
69
46
|
desc 'retry ID', 'Re-queue a generated function for regeneration'
|
|
70
47
|
def retry_generation(id)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
Legion::Extensions::Codegen::Helpers::GeneratedRegistry.update_status(id: id, status: 'pending')
|
|
77
|
-
say Legion::JSON.dump({ data: { id: id, status: 'pending' } })
|
|
48
|
+
data = api_post("/api/codegen/generated/#{id}/retry")
|
|
49
|
+
formatter.json(data)
|
|
78
50
|
end
|
|
79
51
|
map 'retry' => :retry_generation
|
|
80
52
|
|
|
81
53
|
desc 'gaps', 'List detected capability gaps with priorities'
|
|
82
54
|
def gaps
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
say Legion::JSON.dump({ data: detected })
|
|
86
|
-
else
|
|
87
|
-
say Legion::JSON.dump({ error: 'gap detector not available' })
|
|
88
|
-
end
|
|
55
|
+
data = api_get('/api/codegen/gaps')
|
|
56
|
+
formatter.json(data)
|
|
89
57
|
end
|
|
90
58
|
|
|
91
59
|
desc 'cycle', 'Manually trigger a generation cycle (bypass cooldown)'
|
|
92
60
|
def cycle
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
end
|
|
61
|
+
data = api_post('/api/codegen/cycle')
|
|
62
|
+
formatter.json(data)
|
|
63
|
+
end
|
|
97
64
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
65
|
+
no_commands do
|
|
66
|
+
include ApiClient
|
|
67
|
+
|
|
68
|
+
def formatter
|
|
69
|
+
@formatter ||= Output::Formatter.new(json: options[:json], color: !options[:no_color])
|
|
70
|
+
end
|
|
101
71
|
end
|
|
102
72
|
end
|
|
103
73
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'shellwords'
|
|
4
|
+
require_relative 'api_client'
|
|
4
5
|
|
|
5
6
|
module Legion
|
|
6
7
|
module CLI
|
|
@@ -16,14 +17,10 @@ module Legion
|
|
|
16
17
|
option :extensions, type: :string, desc: 'Comma-separated file extensions to watch (e.g. md,rb)'
|
|
17
18
|
option :label, type: :string, desc: 'Human-readable label for this monitor'
|
|
18
19
|
def add(path)
|
|
19
|
-
require_monitor!
|
|
20
|
-
exts = options[:extensions]&.split(',')&.map(&:strip)
|
|
21
|
-
result = Legion::Extensions::Knowledge::Runners::Monitor.add_monitor(
|
|
22
|
-
path: path,
|
|
23
|
-
extensions: exts,
|
|
24
|
-
label: options[:label]
|
|
25
|
-
)
|
|
26
20
|
out = formatter
|
|
21
|
+
exts = options[:extensions]&.split(',')&.map(&:strip)
|
|
22
|
+
result = api_post('/api/knowledge/monitors', path: path, extensions: exts, label: options[:label])
|
|
23
|
+
|
|
27
24
|
if options[:json]
|
|
28
25
|
out.json(result)
|
|
29
26
|
elsif result[:success]
|
|
@@ -35,9 +32,9 @@ module Legion
|
|
|
35
32
|
|
|
36
33
|
desc 'list', 'List registered corpus monitors'
|
|
37
34
|
def list
|
|
38
|
-
require_monitor!
|
|
39
|
-
monitors = Legion::Extensions::Knowledge::Runners::Monitor.list_monitors
|
|
40
35
|
out = formatter
|
|
36
|
+
monitors = api_get('/api/knowledge/monitors')
|
|
37
|
+
|
|
41
38
|
if options[:json]
|
|
42
39
|
out.json(monitors)
|
|
43
40
|
elsif monitors.nil? || monitors.empty?
|
|
@@ -56,9 +53,9 @@ module Legion
|
|
|
56
53
|
|
|
57
54
|
desc 'remove IDENTIFIER', 'Remove a corpus monitor by path or label'
|
|
58
55
|
def remove(identifier)
|
|
59
|
-
require_monitor!
|
|
60
|
-
result = Legion::Extensions::Knowledge::Runners::Monitor.remove_monitor(identifier:)
|
|
61
56
|
out = formatter
|
|
57
|
+
result = api_delete("/api/knowledge/monitors?identifier=#{URI.encode_www_form_component(identifier)}")
|
|
58
|
+
|
|
62
59
|
if options[:json]
|
|
63
60
|
out.json(result)
|
|
64
61
|
elsif result[:success]
|
|
@@ -70,9 +67,9 @@ module Legion
|
|
|
70
67
|
|
|
71
68
|
desc 'status', 'Show monitor status (counts)'
|
|
72
69
|
def status
|
|
73
|
-
require_monitor!
|
|
74
|
-
result = Legion::Extensions::Knowledge::Runners::Monitor.monitor_status
|
|
75
70
|
out = formatter
|
|
71
|
+
result = api_get('/api/knowledge/monitors/status')
|
|
72
|
+
|
|
76
73
|
if options[:json]
|
|
77
74
|
out.json(result)
|
|
78
75
|
else
|
|
@@ -85,13 +82,11 @@ module Legion
|
|
|
85
82
|
end
|
|
86
83
|
|
|
87
84
|
no_commands do
|
|
85
|
+
include ApiClient
|
|
86
|
+
|
|
88
87
|
def formatter
|
|
89
88
|
@formatter ||= Output::Formatter.new(json: options[:json], color: !options[:no_color])
|
|
90
89
|
end
|
|
91
|
-
|
|
92
|
-
def require_monitor!
|
|
93
|
-
Connection.ensure_knowledge unless defined?(Legion::Extensions::Knowledge::Runners::Monitor)
|
|
94
|
-
end
|
|
95
90
|
end
|
|
96
91
|
end
|
|
97
92
|
|
|
@@ -118,12 +113,7 @@ module Legion
|
|
|
118
113
|
content = "Git commit: #{sha}\nSubject: #{subject}\n\nDiff stat:\n#{diff_stat}"
|
|
119
114
|
tags = %w[git commit knowledge-capture]
|
|
120
115
|
|
|
121
|
-
|
|
122
|
-
result = Legion::Extensions::Knowledge::Runners::Ingest.ingest_file(
|
|
123
|
-
content: content,
|
|
124
|
-
tags: tags,
|
|
125
|
-
source: "git:#{sha}"
|
|
126
|
-
)
|
|
116
|
+
result = api_post('/api/knowledge/ingest', content: content, tags: tags, source: "git:#{sha}")
|
|
127
117
|
|
|
128
118
|
out = formatter
|
|
129
119
|
if options[:json]
|
|
@@ -150,12 +140,8 @@ module Legion
|
|
|
150
140
|
tags = ['session', 'knowledge-capture', ::Time.now.strftime('%Y-%m-%d')]
|
|
151
141
|
tags << "repo:#{repo}" unless repo.empty?
|
|
152
142
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
content: content,
|
|
156
|
-
tags: tags,
|
|
157
|
-
source: "session:#{::Time.now.iso8601}"
|
|
158
|
-
)
|
|
143
|
+
result = api_post('/api/knowledge/ingest',
|
|
144
|
+
content: content, tags: tags, source: "session:#{::Time.now.iso8601}")
|
|
159
145
|
|
|
160
146
|
out = formatter
|
|
161
147
|
if options[:json]
|
|
@@ -201,11 +187,10 @@ module Legion
|
|
|
201
187
|
content = format_turn(turn, idx + 1)
|
|
202
188
|
next if content.strip.empty?
|
|
203
189
|
|
|
204
|
-
result =
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
)
|
|
190
|
+
result = api_post('/api/knowledge/ingest',
|
|
191
|
+
content: content,
|
|
192
|
+
tags: base_tags + ["turn:#{idx + 1}"],
|
|
193
|
+
source: "claude-code:#{session_id}:turn-#{idx + 1}")
|
|
209
194
|
ingested += 1 if result[:success]
|
|
210
195
|
end
|
|
211
196
|
|
|
@@ -217,7 +202,9 @@ module Legion
|
|
|
217
202
|
end
|
|
218
203
|
end
|
|
219
204
|
|
|
220
|
-
no_commands do
|
|
205
|
+
no_commands do
|
|
206
|
+
include ApiClient
|
|
207
|
+
|
|
221
208
|
def formatter
|
|
222
209
|
@formatter ||= Output::Formatter.new(json: options[:json], color: !options[:no_color])
|
|
223
210
|
end
|
|
@@ -279,18 +266,6 @@ module Legion
|
|
|
279
266
|
|
|
280
267
|
"#{text.byteslice(0, max_bytes - 20)}\n\n[truncated]"
|
|
281
268
|
end
|
|
282
|
-
|
|
283
|
-
def ingest_content(content:, tags:, source:)
|
|
284
|
-
if defined?(Legion::Extensions::Knowledge::Runners::Ingest)
|
|
285
|
-
Legion::Extensions::Knowledge::Runners::Ingest.ingest_file(
|
|
286
|
-
content: content, tags: tags, source: source
|
|
287
|
-
)
|
|
288
|
-
elsif defined?(Legion::Apollo)
|
|
289
|
-
Legion::Apollo.ingest(content: content, tags: tags, source: source)
|
|
290
|
-
else
|
|
291
|
-
{ success: false, error: 'neither lex-knowledge nor legion-apollo available' }
|
|
292
|
-
end
|
|
293
|
-
end
|
|
294
269
|
end
|
|
295
270
|
end
|
|
296
271
|
|
|
@@ -307,9 +282,8 @@ module Legion
|
|
|
307
282
|
option :synthesize, type: :boolean, default: true, desc: 'Synthesize an LLM answer'
|
|
308
283
|
option :verbose, type: :boolean, default: false, desc: 'Show full source metadata'
|
|
309
284
|
def query(question)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
synthesize: options[:synthesize])
|
|
285
|
+
result = api_post('/api/knowledge/query',
|
|
286
|
+
question: question, top_k: options[:top_k], synthesize: options[:synthesize])
|
|
313
287
|
out = formatter
|
|
314
288
|
if options[:json]
|
|
315
289
|
out.json(result)
|
|
@@ -330,8 +304,7 @@ module Legion
|
|
|
330
304
|
desc 'retrieve QUESTION', 'Retrieve source chunks without LLM synthesis'
|
|
331
305
|
option :top_k, type: :numeric, default: 5, desc: 'Number of source chunks'
|
|
332
306
|
def retrieve(question)
|
|
333
|
-
|
|
334
|
-
result = knowledge_query.retrieve(question: question, top_k: options[:top_k])
|
|
307
|
+
result = api_post('/api/knowledge/retrieve', question: question, top_k: options[:top_k])
|
|
335
308
|
out = formatter
|
|
336
309
|
if options[:json]
|
|
337
310
|
out.json(result)
|
|
@@ -347,14 +320,8 @@ module Legion
|
|
|
347
320
|
option :force, type: :boolean, default: false, desc: 'Re-ingest even unchanged files'
|
|
348
321
|
option :dry_run, type: :boolean, default: false, desc: 'Preview without writing'
|
|
349
322
|
def ingest(path)
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
knowledge_ingest.ingest_corpus(path: path, force: options[:force],
|
|
353
|
-
dry_run: options[:dry_run])
|
|
354
|
-
else
|
|
355
|
-
knowledge_ingest.ingest_file(file_path: path, force: options[:force],
|
|
356
|
-
dry_run: options[:dry_run])
|
|
357
|
-
end
|
|
323
|
+
result = api_post('/api/knowledge/ingest',
|
|
324
|
+
path: ::File.expand_path(path), force: options[:force], dry_run: options[:dry_run])
|
|
358
325
|
out = formatter
|
|
359
326
|
if options[:json]
|
|
360
327
|
out.json(result)
|
|
@@ -368,8 +335,7 @@ module Legion
|
|
|
368
335
|
|
|
369
336
|
desc 'status', 'Show knowledge base status'
|
|
370
337
|
def status
|
|
371
|
-
|
|
372
|
-
result = knowledge_ingest.scan_corpus(path: ::Dir.pwd)
|
|
338
|
+
result = api_post('/api/knowledge/status', path: ::Dir.pwd)
|
|
373
339
|
out = formatter
|
|
374
340
|
if options[:json]
|
|
375
341
|
out.json(result)
|
|
@@ -386,9 +352,7 @@ module Legion
|
|
|
386
352
|
desc 'health', 'Show knowledge base health report (local, Apollo, sync)'
|
|
387
353
|
option :corpus_path, type: :string, desc: 'Path to corpus directory (falls back to settings)'
|
|
388
354
|
def health
|
|
389
|
-
|
|
390
|
-
path = resolve_corpus_path
|
|
391
|
-
result = knowledge_maintenance.health(path: path)
|
|
355
|
+
result = api_post('/api/knowledge/health', path: options[:corpus_path])
|
|
392
356
|
out = formatter
|
|
393
357
|
if options[:json]
|
|
394
358
|
out.json(result)
|
|
@@ -412,9 +376,8 @@ module Legion
|
|
|
412
376
|
option :corpus_path, type: :string, desc: 'Path to corpus directory (falls back to settings)'
|
|
413
377
|
option :dry_run, type: :boolean, default: true, desc: 'Preview without archiving (default: true)'
|
|
414
378
|
def maintain
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
result = knowledge_maintenance.cleanup_orphans(path: path, dry_run: options[:dry_run])
|
|
379
|
+
result = api_post('/api/knowledge/maintain',
|
|
380
|
+
path: options[:corpus_path], dry_run: options[:dry_run])
|
|
418
381
|
out = formatter
|
|
419
382
|
if options[:json]
|
|
420
383
|
out.json(result)
|
|
@@ -434,8 +397,7 @@ module Legion
|
|
|
434
397
|
desc 'quality', 'Show knowledge quality report (hot, cold, low-confidence chunks)'
|
|
435
398
|
option :limit, type: :numeric, default: 10, desc: 'Max entries per category'
|
|
436
399
|
def quality
|
|
437
|
-
|
|
438
|
-
result = knowledge_maintenance.quality_report(limit: options[:limit])
|
|
400
|
+
result = api_post('/api/knowledge/quality', limit: options[:limit])
|
|
439
401
|
out = formatter
|
|
440
402
|
if options[:json]
|
|
441
403
|
out.json(result)
|
|
@@ -459,54 +421,13 @@ module Legion
|
|
|
459
421
|
desc 'capture SUBCOMMAND', 'Capture knowledge from git commits or sessions'
|
|
460
422
|
subcommand 'capture', CaptureCommand
|
|
461
423
|
|
|
462
|
-
no_commands do
|
|
424
|
+
no_commands do
|
|
425
|
+
include ApiClient
|
|
426
|
+
|
|
463
427
|
def formatter
|
|
464
428
|
@formatter ||= Output::Formatter.new(json: options[:json], color: !options[:no_color])
|
|
465
429
|
end
|
|
466
430
|
|
|
467
|
-
def require_knowledge!
|
|
468
|
-
Connection.ensure_knowledge unless defined?(Legion::Extensions::Knowledge::Runners::Query)
|
|
469
|
-
end
|
|
470
|
-
|
|
471
|
-
def require_ingest!
|
|
472
|
-
Connection.ensure_knowledge unless defined?(Legion::Extensions::Knowledge::Runners::Ingest)
|
|
473
|
-
end
|
|
474
|
-
|
|
475
|
-
def require_maintenance!
|
|
476
|
-
Connection.ensure_knowledge unless defined?(Legion::Extensions::Knowledge::Runners::Maintenance)
|
|
477
|
-
end
|
|
478
|
-
|
|
479
|
-
def knowledge_query
|
|
480
|
-
Legion::Extensions::Knowledge::Runners::Query
|
|
481
|
-
end
|
|
482
|
-
|
|
483
|
-
def knowledge_ingest
|
|
484
|
-
Legion::Extensions::Knowledge::Runners::Ingest
|
|
485
|
-
end
|
|
486
|
-
|
|
487
|
-
def knowledge_maintenance
|
|
488
|
-
Legion::Extensions::Knowledge::Runners::Maintenance
|
|
489
|
-
end
|
|
490
|
-
|
|
491
|
-
def resolve_corpus_path
|
|
492
|
-
if options[:corpus_path]
|
|
493
|
-
options[:corpus_path]
|
|
494
|
-
elsif defined?(Legion::Extensions::Knowledge::Runners::Monitor)
|
|
495
|
-
monitors = Legion::Extensions::Knowledge::Runners::Monitor.resolve_monitors
|
|
496
|
-
monitors.first&.dig(:path) || legacy_corpus_path || ::Dir.pwd
|
|
497
|
-
elsif defined?(Legion::Settings)
|
|
498
|
-
Legion::Settings.dig(:knowledge, :corpus_path) || ::Dir.pwd
|
|
499
|
-
else
|
|
500
|
-
::Dir.pwd
|
|
501
|
-
end
|
|
502
|
-
end
|
|
503
|
-
|
|
504
|
-
def legacy_corpus_path
|
|
505
|
-
return unless defined?(Legion::Settings)
|
|
506
|
-
|
|
507
|
-
Legion::Settings.dig(:knowledge, :corpus_path)
|
|
508
|
-
end
|
|
509
|
-
|
|
510
431
|
def print_sources(sources, out, verbose:)
|
|
511
432
|
return out.warn('No sources found') if sources.empty?
|
|
512
433
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'api_client'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module CLI
|
|
5
7
|
class Schedule < Thor
|
|
@@ -17,22 +19,20 @@ module Legion
|
|
|
17
19
|
option :limit, type: :numeric, default: 20, desc: 'Max results'
|
|
18
20
|
def list
|
|
19
21
|
out = formatter
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
out.status(s[:active] ? 'active' : 'inactive'), s[:description] || '-']
|
|
32
|
-
end
|
|
33
|
-
out.table(%w[ID Function Schedule Status Description], rows)
|
|
34
|
-
puts " #{schedules.size} schedule(s)"
|
|
22
|
+
query = "/api/schedules?limit=#{options[:limit]}"
|
|
23
|
+
query += '&active=true' if options[:active]
|
|
24
|
+
schedules = api_get(query)
|
|
25
|
+
schedules = [] if schedules.nil?
|
|
26
|
+
|
|
27
|
+
if options[:json]
|
|
28
|
+
out.json(schedules)
|
|
29
|
+
else
|
|
30
|
+
rows = Array(schedules).map do |s|
|
|
31
|
+
[s[:id], s[:function_id] || '-', s[:cron] || s[:interval] || '-',
|
|
32
|
+
out.status(s[:active] ? 'active' : 'inactive'), s[:description] || '-']
|
|
35
33
|
end
|
|
34
|
+
out.table(%w[ID Function Schedule Status Description], rows)
|
|
35
|
+
puts " #{rows.size} schedule(s)"
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
default_task :list
|
|
@@ -40,21 +40,14 @@ module Legion
|
|
|
40
40
|
desc 'show ID', 'Show schedule details'
|
|
41
41
|
def show(id)
|
|
42
42
|
out = formatter
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if options[:json]
|
|
52
|
-
out.json(schedule.values)
|
|
53
|
-
else
|
|
54
|
-
out.header("Schedule ##{id}")
|
|
55
|
-
out.spacer
|
|
56
|
-
out.detail(schedule.values.transform_keys(&:to_s))
|
|
57
|
-
end
|
|
43
|
+
schedule = api_get("/api/schedules/#{id}")
|
|
44
|
+
|
|
45
|
+
if options[:json]
|
|
46
|
+
out.json(schedule)
|
|
47
|
+
else
|
|
48
|
+
out.header("Schedule ##{id}")
|
|
49
|
+
out.spacer
|
|
50
|
+
out.detail(schedule.transform_keys(&:to_s))
|
|
58
51
|
end
|
|
59
52
|
end
|
|
60
53
|
|
|
@@ -65,24 +58,22 @@ module Legion
|
|
|
65
58
|
option :description, type: :string, desc: 'Schedule description'
|
|
66
59
|
def add
|
|
67
60
|
out = formatter
|
|
68
|
-
with_data do
|
|
69
|
-
require_scheduler!
|
|
70
|
-
attrs = { function_id: options[:function_id], active: true, created_at: Time.now.utc }
|
|
71
|
-
attrs[:cron] = options[:cron] if options[:cron]
|
|
72
|
-
attrs[:interval] = options[:interval] if options[:interval]
|
|
73
|
-
attrs[:description] = options[:description] if options[:description]
|
|
74
|
-
|
|
75
|
-
unless attrs[:cron] || attrs[:interval]
|
|
76
|
-
out.error('Either --cron or --interval is required')
|
|
77
|
-
return
|
|
78
|
-
end
|
|
79
61
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
62
|
+
unless options[:cron] || options[:interval]
|
|
63
|
+
out.error('Either --cron or --interval is required')
|
|
64
|
+
return
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
payload = { function_id: options[:function_id], active: true }
|
|
68
|
+
payload[:cron] = options[:cron] if options[:cron]
|
|
69
|
+
payload[:interval] = options[:interval] if options[:interval]
|
|
70
|
+
payload[:description] = options[:description] if options[:description]
|
|
71
|
+
|
|
72
|
+
result = api_post('/api/schedules', **payload)
|
|
73
|
+
if options[:json]
|
|
74
|
+
out.json(result)
|
|
75
|
+
else
|
|
76
|
+
out.success("Schedule ##{result[:id]} created")
|
|
86
77
|
end
|
|
87
78
|
end
|
|
88
79
|
|
|
@@ -90,25 +81,17 @@ module Legion
|
|
|
90
81
|
option :yes, type: :boolean, default: false, aliases: '-y', desc: 'Skip confirmation'
|
|
91
82
|
def remove(id)
|
|
92
83
|
out = formatter
|
|
93
|
-
with_data do
|
|
94
|
-
require_scheduler!
|
|
95
|
-
schedule = Legion::Extensions::Scheduler::Data::Model::Schedule[id.to_i]
|
|
96
|
-
unless schedule
|
|
97
|
-
out.error("Schedule not found: #{id}")
|
|
98
|
-
return
|
|
99
|
-
end
|
|
100
84
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
85
|
+
unless options[:yes]
|
|
86
|
+
print "Delete schedule ##{id}? [y/N] "
|
|
87
|
+
return unless $stdin.gets&.strip&.downcase == 'y'
|
|
88
|
+
end
|
|
105
89
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
end
|
|
90
|
+
result = api_delete("/api/schedules/#{id}")
|
|
91
|
+
if options[:json]
|
|
92
|
+
out.json({ id: id.to_i, deleted: true }.merge(result || {}))
|
|
93
|
+
else
|
|
94
|
+
out.success("Schedule ##{id} deleted")
|
|
112
95
|
end
|
|
113
96
|
end
|
|
114
97
|
|
|
@@ -116,58 +99,33 @@ module Legion
|
|
|
116
99
|
option :limit, type: :numeric, default: 20, desc: 'Max results'
|
|
117
100
|
def logs(id)
|
|
118
101
|
out = formatter
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
.where(schedule_id: id.to_i)
|
|
129
|
-
.order(Sequel.desc(:id))
|
|
130
|
-
.limit(options[:limit]).all
|
|
131
|
-
|
|
132
|
-
if options[:json]
|
|
133
|
-
out.json(log_entries.map(&:values))
|
|
102
|
+
log_entries = api_get("/api/schedules/#{id}/logs?limit=#{options[:limit]}")
|
|
103
|
+
log_entries = [] if log_entries.nil?
|
|
104
|
+
|
|
105
|
+
if options[:json]
|
|
106
|
+
out.json(log_entries)
|
|
107
|
+
else
|
|
108
|
+
out.header("Logs for Schedule ##{id}")
|
|
109
|
+
if Array(log_entries).empty?
|
|
110
|
+
puts ' No logs found.'
|
|
134
111
|
else
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
puts ' No logs found.'
|
|
138
|
-
else
|
|
139
|
-
rows = log_entries.map { |l| [l[:id], l[:status] || '-', l[:started_at]&.to_s || '-', l[:message] || '-'] }
|
|
140
|
-
out.table(%w[ID Status Started Message], rows)
|
|
112
|
+
rows = Array(log_entries).map do |l|
|
|
113
|
+
[l[:id], l[:status] || '-', l[:started_at]&.to_s || '-', l[:message] || '-']
|
|
141
114
|
end
|
|
115
|
+
out.table(%w[ID Status Started Message], rows)
|
|
142
116
|
end
|
|
143
117
|
end
|
|
144
118
|
end
|
|
145
119
|
|
|
146
120
|
no_commands do
|
|
121
|
+
include ApiClient
|
|
122
|
+
|
|
147
123
|
def formatter
|
|
148
124
|
@formatter ||= Output::Formatter.new(
|
|
149
125
|
json: options[:json],
|
|
150
126
|
color: !options[:no_color]
|
|
151
127
|
)
|
|
152
128
|
end
|
|
153
|
-
|
|
154
|
-
def with_data
|
|
155
|
-
Connection.config_dir = options[:config_dir] if options[:config_dir]
|
|
156
|
-
Connection.log_level = options[:verbose] ? 'debug' : 'error'
|
|
157
|
-
Connection.ensure_data
|
|
158
|
-
yield
|
|
159
|
-
rescue CLI::Error => e
|
|
160
|
-
formatter.error(e.message)
|
|
161
|
-
raise SystemExit, 1
|
|
162
|
-
ensure
|
|
163
|
-
Connection.shutdown
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def require_scheduler!
|
|
167
|
-
return if defined?(Legion::Extensions::Scheduler::Data::Model::Schedule)
|
|
168
|
-
|
|
169
|
-
raise CLI::Error, 'lex-scheduler extension is not loaded. Install and enable it first.'
|
|
170
|
-
end
|
|
171
129
|
end
|
|
172
130
|
end
|
|
173
131
|
end
|
data/lib/legion/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: legionio
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.6.
|
|
4
|
+
version: 1.6.45
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -489,6 +489,7 @@ files:
|
|
|
489
489
|
- lib/legion/api/graphql/types/task_type.rb
|
|
490
490
|
- lib/legion/api/graphql/types/worker_type.rb
|
|
491
491
|
- lib/legion/api/helpers.rb
|
|
492
|
+
- lib/legion/api/knowledge.rb
|
|
492
493
|
- lib/legion/api/lex_dispatch.rb
|
|
493
494
|
- lib/legion/api/library_routes.rb
|
|
494
495
|
- lib/legion/api/llm.rb
|
|
@@ -540,6 +541,7 @@ files:
|
|
|
540
541
|
- lib/legion/cli/acp_command.rb
|
|
541
542
|
- lib/legion/cli/admin/purge_topology.rb
|
|
542
543
|
- lib/legion/cli/admin_command.rb
|
|
544
|
+
- lib/legion/cli/api_client.rb
|
|
543
545
|
- lib/legion/cli/apollo_command.rb
|
|
544
546
|
- lib/legion/cli/audit_command.rb
|
|
545
547
|
- lib/legion/cli/auth_command.rb
|