legionio 1.4.101 → 1.4.102
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/.rubocop.yml +1 -0
- data/CHANGELOG.md +7 -0
- data/lib/legion/cli/image_command.rb +164 -0
- data/lib/legion/cli.rb +4 -0
- 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: 4d271552f43fd7cd244b7e22845d5f08b0a8adba77c159ae6054ab913f2cbea1
|
|
4
|
+
data.tar.gz: 761a64ecc11ca421b45c8d618e8d2e7dc4dcf7d53c231b79e9bae805580d0ce5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6fa6a1c07d50d1283608f175016ff4833f6518269bb5a5b5c9e0a5b8484bb5784918115648fd8ec64762a05676b3593f5c5fc27585ba5f6d1940b39bfc0466e7
|
|
7
|
+
data.tar.gz: 03ab72c8a44c4c2a83146d343c8824bd9eaae5cd3cd4eb1bd6e31c843f9e2622e7e4dce306c19d34769e3e703ab6315cf469336c2a0515fcc7e604b0b7ab33fd
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Legion Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.102] - 2026-03-21
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `legion image analyze PATH` — analyze an image file via LLM; supports `--prompt`, `--model`, `--provider`, `--format text|json`
|
|
7
|
+
- `legion image compare PATH1 PATH2` — compare two images side by side via LLM with same options
|
|
8
|
+
- Supports png, jpg, jpeg, gif, webp; base64-encodes image data and builds multimodal content blocks for the LLM message
|
|
9
|
+
|
|
3
10
|
## [1.4.101] - 2026-03-21
|
|
4
11
|
|
|
5
12
|
### Fixed
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
require 'base64'
|
|
5
|
+
|
|
6
|
+
module Legion
|
|
7
|
+
module CLI
|
|
8
|
+
class Image < Thor
|
|
9
|
+
def self.exit_on_failure?
|
|
10
|
+
true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
SUPPORTED_TYPES = %w[png jpg jpeg gif webp].freeze
|
|
14
|
+
|
|
15
|
+
MIME_TYPES = {
|
|
16
|
+
'png' => 'image/png',
|
|
17
|
+
'jpg' => 'image/jpeg',
|
|
18
|
+
'jpeg' => 'image/jpeg',
|
|
19
|
+
'gif' => 'image/gif',
|
|
20
|
+
'webp' => 'image/webp'
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
class_option :json, type: :boolean, default: false, desc: 'Output as JSON'
|
|
24
|
+
class_option :no_color, type: :boolean, default: false, desc: 'Disable color output'
|
|
25
|
+
class_option :verbose, type: :boolean, default: false, aliases: ['-V'], desc: 'Verbose logging'
|
|
26
|
+
class_option :config_dir, type: :string, desc: 'Config directory path'
|
|
27
|
+
|
|
28
|
+
desc 'analyze PATH', 'Analyze an image file using an LLM'
|
|
29
|
+
option :prompt, type: :string, aliases: ['-p'],
|
|
30
|
+
desc: 'Custom question to ask about the image',
|
|
31
|
+
default: 'Describe this image in detail'
|
|
32
|
+
option :model, type: :string, aliases: ['-m'], desc: 'LLM model override'
|
|
33
|
+
option :provider, type: :string, desc: 'LLM provider override'
|
|
34
|
+
option :format, type: :string, default: 'text', desc: 'Output format: text or json'
|
|
35
|
+
def analyze(path)
|
|
36
|
+
out = formatter
|
|
37
|
+
setup_connection(out)
|
|
38
|
+
|
|
39
|
+
image_data = load_image(path, out)
|
|
40
|
+
return unless image_data
|
|
41
|
+
|
|
42
|
+
messages = [build_image_message([image_data], options[:prompt])]
|
|
43
|
+
response = call_llm(messages, out)
|
|
44
|
+
return unless response
|
|
45
|
+
|
|
46
|
+
render_response(out, response, { path: path, prompt: options[:prompt] })
|
|
47
|
+
rescue CLI::Error => e
|
|
48
|
+
formatter.error(e.message)
|
|
49
|
+
raise SystemExit, 1
|
|
50
|
+
ensure
|
|
51
|
+
Connection.shutdown
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
desc 'compare PATH1 PATH2', 'Compare two images side by side using an LLM'
|
|
55
|
+
option :prompt, type: :string, aliases: ['-p'],
|
|
56
|
+
desc: 'Custom comparison question',
|
|
57
|
+
default: 'Compare these two images and describe the differences'
|
|
58
|
+
option :model, type: :string, aliases: ['-m'], desc: 'LLM model override'
|
|
59
|
+
option :provider, type: :string, desc: 'LLM provider override'
|
|
60
|
+
option :format, type: :string, default: 'text', desc: 'Output format: text or json'
|
|
61
|
+
def compare(path1, path2)
|
|
62
|
+
out = formatter
|
|
63
|
+
setup_connection(out)
|
|
64
|
+
|
|
65
|
+
image1 = load_image(path1, out)
|
|
66
|
+
return unless image1
|
|
67
|
+
|
|
68
|
+
image2 = load_image(path2, out)
|
|
69
|
+
return unless image2
|
|
70
|
+
|
|
71
|
+
messages = [build_image_message([image1, image2], options[:prompt])]
|
|
72
|
+
response = call_llm(messages, out)
|
|
73
|
+
return unless response
|
|
74
|
+
|
|
75
|
+
render_response(out, response, { path1: path1, path2: path2, prompt: options[:prompt] })
|
|
76
|
+
rescue CLI::Error => e
|
|
77
|
+
formatter.error(e.message)
|
|
78
|
+
raise SystemExit, 1
|
|
79
|
+
ensure
|
|
80
|
+
Connection.shutdown
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
no_commands do
|
|
84
|
+
def formatter
|
|
85
|
+
@formatter ||= Output::Formatter.new(
|
|
86
|
+
json: options[:json],
|
|
87
|
+
color: !options[:no_color]
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def setup_connection(out)
|
|
92
|
+
Connection.config_dir = options[:config_dir] if options[:config_dir]
|
|
93
|
+
Connection.log_level = options[:verbose] ? 'debug' : 'error'
|
|
94
|
+
Connection.ensure_llm
|
|
95
|
+
rescue CLI::Error => e
|
|
96
|
+
out.error(e.message)
|
|
97
|
+
raise SystemExit, 1
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def load_image(path, out)
|
|
101
|
+
unless File.exist?(path)
|
|
102
|
+
out.error("File not found: #{path}")
|
|
103
|
+
raise SystemExit, 1
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
ext = File.extname(path).delete_prefix('.').downcase
|
|
107
|
+
unless SUPPORTED_TYPES.include?(ext)
|
|
108
|
+
out.error("Unsupported image type '.#{ext}'. Supported: #{SUPPORTED_TYPES.join(', ')}")
|
|
109
|
+
raise SystemExit, 1
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
{
|
|
113
|
+
path: path,
|
|
114
|
+
mime_type: MIME_TYPES[ext],
|
|
115
|
+
data: Base64.strict_encode64(File.binread(path))
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def build_image_message(images, prompt_text)
|
|
120
|
+
content = images.map do |img|
|
|
121
|
+
{
|
|
122
|
+
type: 'image',
|
|
123
|
+
source: {
|
|
124
|
+
type: 'base64',
|
|
125
|
+
media_type: img[:mime_type],
|
|
126
|
+
data: img[:data]
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
end
|
|
130
|
+
content << { type: 'text', text: prompt_text }
|
|
131
|
+
{ role: 'user', content: content }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def call_llm(messages, out)
|
|
135
|
+
llm_kwargs = {}
|
|
136
|
+
llm_kwargs[:model] = options[:model] if options[:model]
|
|
137
|
+
llm_kwargs[:provider] = options[:provider].to_sym if options[:provider]
|
|
138
|
+
|
|
139
|
+
Legion::LLM.chat(messages: messages, **llm_kwargs)
|
|
140
|
+
rescue StandardError => e
|
|
141
|
+
out.error("LLM call failed: #{e.message}")
|
|
142
|
+
raise SystemExit, 1
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def render_response(out, response, meta)
|
|
146
|
+
content = response[:content].to_s
|
|
147
|
+
usage = response[:usage] || {}
|
|
148
|
+
|
|
149
|
+
if options[:format] == 'json' || options[:json]
|
|
150
|
+
out.json(meta.merge(response: content, usage: usage))
|
|
151
|
+
else
|
|
152
|
+
out.header('Analysis')
|
|
153
|
+
out.spacer
|
|
154
|
+
puts content
|
|
155
|
+
return if usage.nil? || usage.empty?
|
|
156
|
+
|
|
157
|
+
out.spacer
|
|
158
|
+
out.detail(usage)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
data/lib/legion/cli.rb
CHANGED
|
@@ -42,6 +42,7 @@ module Legion
|
|
|
42
42
|
autoload :Init, 'legion/cli/init_command'
|
|
43
43
|
autoload :Skill, 'legion/cli/skill_command'
|
|
44
44
|
autoload :Prompt, 'legion/cli/prompt_command'
|
|
45
|
+
autoload :Image, 'legion/cli/image_command'
|
|
45
46
|
autoload :Dataset, 'legion/cli/dataset_command'
|
|
46
47
|
autoload :Cost, 'legion/cli/cost_command'
|
|
47
48
|
autoload :Marketplace, 'legion/cli/marketplace_command'
|
|
@@ -267,6 +268,9 @@ module Legion
|
|
|
267
268
|
desc 'observe SUBCOMMAND', 'MCP tool observation stats'
|
|
268
269
|
subcommand 'observe', Legion::CLI::ObserveCommand
|
|
269
270
|
|
|
271
|
+
desc 'image SUBCOMMAND', 'Multimodal image analysis and comparison'
|
|
272
|
+
subcommand 'image', Legion::CLI::Image
|
|
273
|
+
|
|
270
274
|
desc 'payroll SUBCOMMAND', 'Workforce cost and labor economics'
|
|
271
275
|
subcommand 'payroll', Legion::CLI::Payroll
|
|
272
276
|
|
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.102
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -472,6 +472,7 @@ files:
|
|
|
472
472
|
- lib/legion/cli/gaia_command.rb
|
|
473
473
|
- lib/legion/cli/generate_command.rb
|
|
474
474
|
- lib/legion/cli/graph_command.rb
|
|
475
|
+
- lib/legion/cli/image_command.rb
|
|
475
476
|
- lib/legion/cli/init/config_generator.rb
|
|
476
477
|
- lib/legion/cli/init/environment_detector.rb
|
|
477
478
|
- lib/legion/cli/init_command.rb
|