legionio 1.4.67 → 1.4.68
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 +15 -0
- data/CLAUDE.md +3 -1
- data/README.md +3 -2
- data/lib/legion/cli/llm_command.rb +346 -0
- data/lib/legion/cli/update_command.rb +1 -0
- data/lib/legion/cli.rb +8 -2
- data/lib/legion/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1521235e6e865bd6d74f41b93c57e159de23d9dee0f044486c8059a6af780ab0
|
|
4
|
+
data.tar.gz: 7bdb02eb37ca2f03b9c0b6f7f494ebbb394d69f53ff83dd4bb5c1e27cb37e85e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 78a7171285d6a6eebda11d87d2782a68b6edb0dbc1f0be6d6034c1b4c7e535a5ae2dc1feb34ecbcc5af64d767759c7af7c78c1c50ed8672a1f08dea738c6b23b
|
|
7
|
+
data.tar.gz: 0b7c0e4d1298e13815383df8667d2b14debf66675c981002847fa19a01d0be6afa4314d811ba7cde7285ee46ddf37e9985e8a86745aef2b4b122d2a7dc1e16ac
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Legion Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.68] - 2026-03-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `legionio llm` subcommand for LLM provider diagnostics
|
|
7
|
+
- `llm status` (default) — show LLM state, enabled providers, routing, system memory
|
|
8
|
+
- `llm providers` — list all providers with enabled/disabled and reachability status
|
|
9
|
+
- `llm models` — list available models per enabled provider (Ollama discovery + cloud defaults)
|
|
10
|
+
- `llm ping` — test connectivity to each enabled provider with latency measurement
|
|
11
|
+
- All subcommands support `--json` output
|
|
12
|
+
- `legionio version` now shows legion-llm, legion-gaia, and legion-tty in components list
|
|
13
|
+
- `legionio version --json` now includes components hash and extension count
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- `legionio update` now correctly detects gem version changes (was showing "already latest" for every gem due to stale in-memory gem spec cache after subprocess install)
|
|
17
|
+
|
|
3
18
|
## [1.4.67] - 2026-03-18
|
|
4
19
|
|
|
5
20
|
### Added
|
data/CLAUDE.md
CHANGED
|
@@ -9,7 +9,7 @@ The primary gem for the LegionIO framework. An extensible async job engine for s
|
|
|
9
9
|
|
|
10
10
|
**GitHub**: https://github.com/LegionIO/LegionIO
|
|
11
11
|
**Gem**: `legionio`
|
|
12
|
-
**Version**: 1.4.
|
|
12
|
+
**Version**: 1.4.67
|
|
13
13
|
**License**: Apache-2.0
|
|
14
14
|
**Docker**: `legionio/legion`
|
|
15
15
|
**Ruby**: >= 3.4
|
|
@@ -501,9 +501,11 @@ legion
|
|
|
501
501
|
| `bootsnap` (>= 1.18) | YARV bytecode + load-path caching |
|
|
502
502
|
| `oj` (>= 3.16) | Fast JSON (C extension) |
|
|
503
503
|
| `puma` (>= 6.0) | HTTP server for API |
|
|
504
|
+
| `rackup` (>= 2.0) | Rack server launcher for MCP HTTP transport |
|
|
504
505
|
| `mcp` (~> 0.8) | MCP server SDK |
|
|
505
506
|
| `reline` (>= 0.5) | Interactive line editing for chat REPL |
|
|
506
507
|
| `rouge` (>= 4.0) | Syntax highlighting for chat markdown rendering |
|
|
508
|
+
| `tty-spinner` (~> 0.9) | Spinner animation for CLI loading states |
|
|
507
509
|
| `sinatra` (>= 4.0) | HTTP API framework |
|
|
508
510
|
| `thor` (>= 1.3) | CLI framework |
|
|
509
511
|
|
data/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Schedule tasks, chain services into dependency graphs, run them concurrently via
|
|
|
14
14
|
╰──────────────────────────────────────╯
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
**Ruby >= 3.4** | **v1.4.
|
|
17
|
+
**Ruby >= 3.4** | **v1.4.67** | **Apache-2.0** | [@Esity](https://github.com/Esity)
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
@@ -83,6 +83,7 @@ gem 'legionio'
|
|
|
83
83
|
| `legion-llm` | AI chat, commit, review, agents, multi-provider LLM routing |
|
|
84
84
|
| `legion-cache` | Redis/Memcached caching for extensions |
|
|
85
85
|
| `legion-crypt` | Vault integration, encryption, JWT auth |
|
|
86
|
+
| `legion-tty` | TTY UI components (spinners, tables, prompts) |
|
|
86
87
|
|
|
87
88
|
## Infrastructure
|
|
88
89
|
|
|
@@ -487,7 +488,7 @@ Each phase registers with `Legion::Readiness`. All phases are individually toggl
|
|
|
487
488
|
git clone https://github.com/LegionIO/LegionIO.git
|
|
488
489
|
cd LegionIO
|
|
489
490
|
bundle install
|
|
490
|
-
bundle exec rspec #
|
|
491
|
+
bundle exec rspec # 0 failures
|
|
491
492
|
bundle exec rubocop # 0 offenses
|
|
492
493
|
```
|
|
493
494
|
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module CLI
|
|
7
|
+
class Llm < Thor
|
|
8
|
+
namespace 'llm'
|
|
9
|
+
|
|
10
|
+
def self.exit_on_failure?
|
|
11
|
+
true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class_option :json, type: :boolean, default: false, desc: 'Output as JSON'
|
|
15
|
+
class_option :no_color, type: :boolean, default: false, desc: 'Disable color output'
|
|
16
|
+
class_option :verbose, type: :boolean, default: false, aliases: ['-V'], desc: 'Verbose logging'
|
|
17
|
+
class_option :config_dir, type: :string, desc: 'Config directory path'
|
|
18
|
+
|
|
19
|
+
desc 'status', 'Show LLM subsystem status and provider health'
|
|
20
|
+
default_task :status
|
|
21
|
+
def status
|
|
22
|
+
out = formatter
|
|
23
|
+
boot_llm_settings
|
|
24
|
+
|
|
25
|
+
data = collect_status
|
|
26
|
+
if options[:json]
|
|
27
|
+
out.json(data)
|
|
28
|
+
else
|
|
29
|
+
show_status(out, data)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
desc 'providers', 'List configured LLM providers'
|
|
34
|
+
def providers
|
|
35
|
+
out = formatter
|
|
36
|
+
boot_llm_settings
|
|
37
|
+
|
|
38
|
+
data = collect_providers
|
|
39
|
+
if options[:json]
|
|
40
|
+
out.json(providers: data)
|
|
41
|
+
else
|
|
42
|
+
show_providers(out, data)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
desc 'models', 'List available models per provider'
|
|
47
|
+
def models
|
|
48
|
+
out = formatter
|
|
49
|
+
boot_llm_settings
|
|
50
|
+
|
|
51
|
+
data = collect_models
|
|
52
|
+
if options[:json]
|
|
53
|
+
out.json(models: data)
|
|
54
|
+
else
|
|
55
|
+
show_models(out, data)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
desc 'ping', 'Test connectivity to each enabled provider'
|
|
60
|
+
option :timeout, type: :numeric, default: 15, desc: 'Timeout per provider in seconds'
|
|
61
|
+
def ping
|
|
62
|
+
out = formatter
|
|
63
|
+
boot_llm(out)
|
|
64
|
+
|
|
65
|
+
results = ping_all_providers(out)
|
|
66
|
+
if options[:json]
|
|
67
|
+
out.json(results: results)
|
|
68
|
+
else
|
|
69
|
+
show_ping_results(out, results)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
no_commands do # rubocop:disable Metrics/BlockLength
|
|
74
|
+
def formatter
|
|
75
|
+
@formatter ||= Output::Formatter.new(
|
|
76
|
+
json: options[:json],
|
|
77
|
+
color: !options[:no_color]
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def boot_llm_settings
|
|
84
|
+
Connection.config_dir = options[:config_dir] if options[:config_dir]
|
|
85
|
+
Connection.log_level = options[:verbose] ? 'debug' : 'error'
|
|
86
|
+
Connection.ensure_settings
|
|
87
|
+
require 'legion/llm'
|
|
88
|
+
Legion::Settings.merge_settings(:llm, Legion::LLM::Settings.default)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def boot_llm(out)
|
|
92
|
+
boot_llm_settings
|
|
93
|
+
out.header('Starting LLM subsystem...') unless options[:json]
|
|
94
|
+
Legion::LLM.start
|
|
95
|
+
rescue StandardError => e
|
|
96
|
+
out.error("LLM start failed: #{e.message}") unless options[:json]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def llm_settings
|
|
100
|
+
Legion::LLM.settings
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def collect_status
|
|
104
|
+
providers_cfg = llm_settings[:providers] || {}
|
|
105
|
+
enabled = providers_cfg.select { |_, c| c[:enabled] }
|
|
106
|
+
started = defined?(Legion::LLM) && Legion::LLM.started?
|
|
107
|
+
|
|
108
|
+
{
|
|
109
|
+
started: started,
|
|
110
|
+
default_model: llm_settings[:default_model],
|
|
111
|
+
default_provider: llm_settings[:default_provider],
|
|
112
|
+
enabled_count: enabled.size,
|
|
113
|
+
total_count: providers_cfg.size,
|
|
114
|
+
providers: collect_providers,
|
|
115
|
+
routing: collect_routing,
|
|
116
|
+
system: collect_system
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def collect_providers
|
|
121
|
+
providers_cfg = llm_settings[:providers] || {}
|
|
122
|
+
providers_cfg.map do |name, cfg|
|
|
123
|
+
{
|
|
124
|
+
name: name,
|
|
125
|
+
enabled: cfg[:enabled] == true,
|
|
126
|
+
default_model: cfg[:default_model],
|
|
127
|
+
reachable: check_reachable(name, cfg)
|
|
128
|
+
}
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def check_reachable(name, cfg)
|
|
133
|
+
case name
|
|
134
|
+
when :ollama
|
|
135
|
+
return false unless cfg[:enabled]
|
|
136
|
+
|
|
137
|
+
base = cfg[:base_url] || 'http://localhost:11434'
|
|
138
|
+
uri = URI(base)
|
|
139
|
+
Socket.tcp(uri.host, uri.port, connect_timeout: 2) { true }
|
|
140
|
+
when :bedrock
|
|
141
|
+
return nil unless cfg[:enabled]
|
|
142
|
+
|
|
143
|
+
cfg[:bearer_token] || (cfg[:api_key] && cfg[:secret_key]) ? :credentials_present : false
|
|
144
|
+
else
|
|
145
|
+
return nil unless cfg[:enabled]
|
|
146
|
+
|
|
147
|
+
cfg[:api_key] ? :credentials_present : false
|
|
148
|
+
end
|
|
149
|
+
rescue StandardError
|
|
150
|
+
false
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def collect_routing
|
|
154
|
+
return { enabled: false } unless defined?(Legion::LLM::Router)
|
|
155
|
+
|
|
156
|
+
{
|
|
157
|
+
enabled: Legion::LLM::Router.routing_enabled?,
|
|
158
|
+
local_tier: Legion::LLM::Router.tier_available?(:local),
|
|
159
|
+
fleet_tier: Legion::LLM::Router.tier_available?(:fleet),
|
|
160
|
+
cloud_tier: Legion::LLM::Router.tier_available?(:cloud)
|
|
161
|
+
}
|
|
162
|
+
rescue StandardError
|
|
163
|
+
{ enabled: false }
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def collect_system
|
|
167
|
+
return {} unless defined?(Legion::LLM::Discovery::System)
|
|
168
|
+
|
|
169
|
+
Legion::LLM::Discovery::System.refresh! if Legion::LLM::Discovery::System.stale?
|
|
170
|
+
{
|
|
171
|
+
platform: Legion::LLM::Discovery::System.platform,
|
|
172
|
+
total_memory_mb: Legion::LLM::Discovery::System.total_memory_mb,
|
|
173
|
+
avail_memory_mb: Legion::LLM::Discovery::System.available_memory_mb,
|
|
174
|
+
memory_pressure: Legion::LLM::Discovery::System.memory_pressure?
|
|
175
|
+
}
|
|
176
|
+
rescue StandardError
|
|
177
|
+
{}
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def collect_models
|
|
181
|
+
providers_cfg = llm_settings[:providers] || {}
|
|
182
|
+
result = {}
|
|
183
|
+
|
|
184
|
+
providers_cfg.each do |name, cfg|
|
|
185
|
+
next unless cfg[:enabled]
|
|
186
|
+
|
|
187
|
+
models = [cfg[:default_model]].compact
|
|
188
|
+
if name == :ollama && defined?(Legion::LLM::Discovery::Ollama)
|
|
189
|
+
begin
|
|
190
|
+
Legion::LLM::Discovery::Ollama.refresh! if Legion::LLM::Discovery::Ollama.stale?
|
|
191
|
+
discovered = Legion::LLM::Discovery::Ollama.model_names
|
|
192
|
+
models = discovered unless discovered.empty?
|
|
193
|
+
rescue StandardError
|
|
194
|
+
# fall back to default_model
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
result[name] = models
|
|
198
|
+
end
|
|
199
|
+
result
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def ping_all_providers(out)
|
|
203
|
+
providers_cfg = llm_settings[:providers] || {}
|
|
204
|
+
enabled = providers_cfg.select { |_, c| c[:enabled] }
|
|
205
|
+
|
|
206
|
+
if enabled.empty?
|
|
207
|
+
out.warn('No providers enabled') unless options[:json]
|
|
208
|
+
return []
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
enabled.map do |name, cfg|
|
|
212
|
+
ping_one_provider(out, name, cfg)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def ping_one_provider(out, name, cfg)
|
|
217
|
+
model = cfg[:default_model]
|
|
218
|
+
return { provider: name, status: 'skip', message: 'no default model configured', latency_ms: nil } unless model
|
|
219
|
+
|
|
220
|
+
out.header(" Pinging #{name} (#{model})...") unless options[:json]
|
|
221
|
+
t0 = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
222
|
+
|
|
223
|
+
response = RubyLLM.chat(model: model, provider: name).ask('Respond with only the word: pong')
|
|
224
|
+
elapsed = ((::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - t0) * 1000).round
|
|
225
|
+
|
|
226
|
+
content = response.content.to_s.strip
|
|
227
|
+
success = content.downcase.include?('pong')
|
|
228
|
+
|
|
229
|
+
if success
|
|
230
|
+
out.success(" #{name}: pong (#{elapsed}ms)") unless options[:json]
|
|
231
|
+
else
|
|
232
|
+
out.warn(" #{name}: unexpected response (#{elapsed}ms): #{content[0..80]}") unless options[:json]
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
{ provider: name, status: success ? 'ok' : 'unexpected', response: content[0..80],
|
|
236
|
+
model: model, latency_ms: elapsed }
|
|
237
|
+
rescue StandardError => e
|
|
238
|
+
elapsed = ((::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - t0) * 1000).round if t0
|
|
239
|
+
|
|
240
|
+
out.error(" #{name}: #{e.message}") unless options[:json]
|
|
241
|
+
{ provider: name, status: 'error', message: e.message, model: model, latency_ms: elapsed }
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def show_status(out, data)
|
|
245
|
+
out.header('LLM Status')
|
|
246
|
+
out.detail({
|
|
247
|
+
'Started' => data[:started].to_s,
|
|
248
|
+
'Default Provider' => (data[:default_provider] || '(none)').to_s,
|
|
249
|
+
'Default Model' => (data[:default_model] || '(none)').to_s,
|
|
250
|
+
'Providers Enabled' => "#{data[:enabled_count]}/#{data[:total_count]}"
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
out.spacer
|
|
254
|
+
show_providers(out, data[:providers])
|
|
255
|
+
|
|
256
|
+
routing = data[:routing] || {}
|
|
257
|
+
if routing[:enabled]
|
|
258
|
+
out.spacer
|
|
259
|
+
out.header('Routing')
|
|
260
|
+
out.detail({
|
|
261
|
+
'Enabled' => routing[:enabled].to_s,
|
|
262
|
+
'Local Tier' => routing[:local_tier].to_s,
|
|
263
|
+
'Fleet Tier' => routing[:fleet_tier].to_s,
|
|
264
|
+
'Cloud Tier' => routing[:cloud_tier].to_s
|
|
265
|
+
})
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
sys = data[:system] || {}
|
|
269
|
+
return if sys.empty?
|
|
270
|
+
|
|
271
|
+
out.spacer
|
|
272
|
+
out.header('System')
|
|
273
|
+
out.detail({
|
|
274
|
+
'Platform' => (sys[:platform] || 'unknown').to_s,
|
|
275
|
+
'Total Memory' => sys[:total_memory_mb] ? "#{sys[:total_memory_mb]} MB" : 'unknown',
|
|
276
|
+
'Available Memory' => sys[:avail_memory_mb] ? "#{sys[:avail_memory_mb]} MB" : 'unknown',
|
|
277
|
+
'Memory Pressure' => sys[:memory_pressure].to_s
|
|
278
|
+
})
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def show_providers(out, providers_data)
|
|
282
|
+
out.header('Providers')
|
|
283
|
+
providers_data.each do |p|
|
|
284
|
+
status = if p[:enabled]
|
|
285
|
+
reach = p[:reachable]
|
|
286
|
+
case reach
|
|
287
|
+
when true then 'enabled, reachable'
|
|
288
|
+
when :credentials_present then 'enabled, credentials present'
|
|
289
|
+
when false then 'enabled, unreachable'
|
|
290
|
+
else 'enabled'
|
|
291
|
+
end
|
|
292
|
+
else
|
|
293
|
+
'disabled'
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
color = p[:enabled] ? :green : :muted
|
|
297
|
+
name_str = p[:name].to_s.ljust(12)
|
|
298
|
+
model_str = p[:default_model] ? " (#{p[:default_model]})" : ''
|
|
299
|
+
puts " #{out.colorize(name_str, :label)}#{out.colorize(status, color)}#{model_str}"
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def show_models(out, models_data)
|
|
304
|
+
out.header('Available Models')
|
|
305
|
+
if models_data.empty?
|
|
306
|
+
out.warn('No providers enabled')
|
|
307
|
+
return
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
models_data.each do |provider, model_list|
|
|
311
|
+
out.spacer
|
|
312
|
+
puts " #{out.colorize(provider.to_s, :accent)} (#{model_list.size} model#{'s' unless model_list.size == 1})"
|
|
313
|
+
model_list.each { |m| puts " #{m}" }
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def show_ping_results(out, results)
|
|
318
|
+
return if results.empty?
|
|
319
|
+
|
|
320
|
+
out.spacer
|
|
321
|
+
out.header('Ping Results')
|
|
322
|
+
passed = 0
|
|
323
|
+
failed = 0
|
|
324
|
+
|
|
325
|
+
results.each do |r|
|
|
326
|
+
case r[:status]
|
|
327
|
+
when 'ok'
|
|
328
|
+
passed += 1
|
|
329
|
+
when 'skip'
|
|
330
|
+
puts " #{out.colorize(r[:provider].to_s.ljust(12), :label)}#{out.colorize('skipped', :muted)} #{r[:message]}"
|
|
331
|
+
else
|
|
332
|
+
failed += 1
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
out.spacer
|
|
337
|
+
if failed.zero?
|
|
338
|
+
out.success("#{passed} provider(s) responding")
|
|
339
|
+
else
|
|
340
|
+
out.error("#{failed} provider(s) failed, #{passed} responding")
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
@@ -33,6 +33,7 @@ module Legion
|
|
|
33
33
|
|
|
34
34
|
before = snapshot_versions(target_gems)
|
|
35
35
|
results = update_gems(target_gems, gem_bin, dry_run: options[:dry_run])
|
|
36
|
+
Gem::Specification.reset unless options[:dry_run]
|
|
36
37
|
after = options[:dry_run] ? before : snapshot_versions(target_gems)
|
|
37
38
|
|
|
38
39
|
if options[:json]
|
data/lib/legion/cli.rb
CHANGED
|
@@ -42,6 +42,7 @@ module Legion
|
|
|
42
42
|
autoload :Cost, 'legion/cli/cost_command'
|
|
43
43
|
autoload :Marketplace, 'legion/cli/marketplace_command'
|
|
44
44
|
autoload :Notebook, 'legion/cli/notebook_command'
|
|
45
|
+
autoload :Llm, 'legion/cli/llm_command'
|
|
45
46
|
autoload :Tty, 'legion/cli/tty_command'
|
|
46
47
|
autoload :Interactive, 'legion/cli/interactive'
|
|
47
48
|
|
|
@@ -60,7 +61,8 @@ module Legion
|
|
|
60
61
|
def version
|
|
61
62
|
out = formatter
|
|
62
63
|
if options[:json]
|
|
63
|
-
out.json(version: Legion::VERSION, ruby: RUBY_VERSION, platform: RUBY_PLATFORM
|
|
64
|
+
out.json(version: Legion::VERSION, ruby: RUBY_VERSION, platform: RUBY_PLATFORM,
|
|
65
|
+
components: installed_components, extensions: discovered_lexs.size)
|
|
64
66
|
else
|
|
65
67
|
out.banner(version: Legion::VERSION)
|
|
66
68
|
out.spacer
|
|
@@ -233,6 +235,9 @@ module Legion
|
|
|
233
235
|
desc 'notebook', 'Read and export Jupyter notebooks'
|
|
234
236
|
subcommand 'notebook', Legion::CLI::Notebook
|
|
235
237
|
|
|
238
|
+
desc 'llm', 'LLM provider diagnostics (status, ping, models)'
|
|
239
|
+
subcommand 'llm', Legion::CLI::Llm
|
|
240
|
+
|
|
236
241
|
desc 'tty', 'Rich terminal UI (onboarding, AI chat, dashboard)'
|
|
237
242
|
subcommand 'tty', Legion::CLI::Tty
|
|
238
243
|
|
|
@@ -304,7 +309,8 @@ module Legion
|
|
|
304
309
|
|
|
305
310
|
def installed_components
|
|
306
311
|
components = { legionio: Legion::VERSION }
|
|
307
|
-
%w[legion-transport legion-data legion-cache legion-crypt legion-json legion-logging legion-settings
|
|
312
|
+
%w[legion-transport legion-data legion-cache legion-crypt legion-json legion-logging legion-settings
|
|
313
|
+
legion-llm legion-gaia legion-tty].each do |gem_name|
|
|
308
314
|
spec = Gem::Specification.find_by_name(gem_name)
|
|
309
315
|
short = gem_name.sub('legion-', '')
|
|
310
316
|
components[short.to_sym] = spec.version.to_s
|
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.4.
|
|
4
|
+
version: 1.4.68
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -484,6 +484,7 @@ files:
|
|
|
484
484
|
- lib/legion/cli/lex/templates/runner_spec.erb
|
|
485
485
|
- lib/legion/cli/lex_command.rb
|
|
486
486
|
- lib/legion/cli/lex_templates.rb
|
|
487
|
+
- lib/legion/cli/llm_command.rb
|
|
487
488
|
- lib/legion/cli/marketplace_command.rb
|
|
488
489
|
- lib/legion/cli/mcp_command.rb
|
|
489
490
|
- lib/legion/cli/memory_command.rb
|