legionio 1.7.34 → 1.7.35

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: 4f7058dc8cbf92828614c9767e67004f572a6f2d076295020245f73f6d5dbf38
4
- data.tar.gz: 64f195574809ad7f3f2962738a17748401a7153775bcc4afa492144a0b44294c
3
+ metadata.gz: 04e534c0bd277abe9dbf19d7087e1ed31a468061bd8c9e784fe94c46975d3b14
4
+ data.tar.gz: f5b7705b447d9df2f9e579f0f0fb7530c418293e0250d3e55b50238ff6989a86
5
5
  SHA512:
6
- metadata.gz: 7170ca9aa9d93cbb366b499120d996fbd8dec86e13ef7951f0137eaf610c3e3f0604b7889c7ddbbd303f9a3bf53f0e7747331b46907101272ca1e2b1e2f68815
7
- data.tar.gz: 0ae25c6ee83ce334fb1d481652facc04898b2d184ec1f02f274e9eec96d320342e4149eabd3f27adaf5118351ad72ea74c22eb4e14147f07809c12d8a58ec6f9
6
+ metadata.gz: 86290b73ddb0168fc3b012e4c4d598ae52de345e61abd29f64b24f446e1714bec2736442a8861c700cb482bf87350a16011e04b7eaa0c05fc3cd7e44562d4ea5
7
+ data.tar.gz: 0b4b02ff3603adc13602fca2e4d9da48ae38be3bb03a97b132f534fd0c74e8eb1969a6bd1c2abd3bf888a59d03a2b2a2fdff308f533aee83377a088e707ee18d
data/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@
5
5
  ### Added
6
6
  - register_credential_providers step in boot sequence for Phase 8 credential-only identity module registration with Broker
7
7
 
8
+ ## [1.7.35] - 2026-04-09
9
+
10
+ ### Added
11
+ - `Legion::Python` central module — single source of truth for venv paths, package list, and interpreter resolution
12
+ - `legionio setup python` CLI command for creating/repairing Python venv with document/data packages
13
+ - `PythonEnvCheck` doctor check for Python venv health
14
+ - Homebrew packaging note: `LEGION_PYTHON` and `LEGION_PYTHON_VENV` are exported by Homebrew wrapper scripts in the companion tap, not by changes in this gem repository
15
+
16
+ ### Fixed
17
+ - `notebook create` crash: removed `python:` kwarg that `Generator.generate` does not accept (`ArgumentError`)
18
+ - `docs serve` now uses `Legion::Python.interpreter` instead of inline path resolution
19
+
8
20
  ## [1.7.33] - 2026-04-09
9
21
 
10
22
  ### Added
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'thor'
4
4
  require 'legion/cli/output'
5
+ require 'legion/python'
5
6
 
6
7
  module Legion
7
8
  module CLI
@@ -50,7 +51,7 @@ module Legion
50
51
  puts " Open http://localhost:#{port}/ in your browser"
51
52
  puts " Serving files from: #{File.expand_path(dir)}"
52
53
  puts ''
53
- puts " To start: python3 -m http.server #{port} --directory #{dir}"
54
+ puts " To start: #{Legion::Python.interpreter} -m http.server #{port} --directory #{dir}"
54
55
  puts ' Press Ctrl+C to stop'
55
56
  end
56
57
 
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'open3'
5
+ require 'legion/python'
6
+
7
+ module Legion
8
+ module CLI
9
+ class Doctor
10
+ class PythonEnvCheck
11
+ def name
12
+ 'Python env'
13
+ end
14
+
15
+ def run
16
+ return skip_result('python3 not found') unless Legion::Python.find_system_python3
17
+ unless Legion::Python.venv_exists?
18
+ return warn_result(
19
+ 'Python venv missing',
20
+ 'Run: legionio setup python',
21
+ auto_fixable: true
22
+ )
23
+ end
24
+
25
+ unless Legion::Python.venv_pip_exists?
26
+ return warn_result(
27
+ 'pip not found in venv — venv may be corrupt',
28
+ 'Run: legionio setup python --rebuild',
29
+ auto_fixable: true
30
+ )
31
+ end
32
+
33
+ unless Legion::Python.venv_python_exists?
34
+ return warn_result(
35
+ 'python3 not found in venv — venv may be corrupt',
36
+ 'Run: legionio setup python --rebuild',
37
+ auto_fixable: true
38
+ )
39
+ end
40
+
41
+ missing = missing_packages
42
+ if missing.any?
43
+ return warn_result(
44
+ "Missing packages: #{missing.join(', ')}",
45
+ 'Run: legionio setup python',
46
+ auto_fixable: true
47
+ )
48
+ end
49
+
50
+ pass_result(venv_summary)
51
+ rescue StandardError => e
52
+ Legion::Logging.error("PythonEnvCheck#run: #{e.message}") if defined?(Legion::Logging)
53
+ Result.new(
54
+ name: name,
55
+ status: :fail,
56
+ message: "Python env check error: #{e.message}",
57
+ prescription: 'Run: legionio setup python'
58
+ )
59
+ end
60
+
61
+ def fix
62
+ system('legionio', 'setup', 'python', '--rebuild')
63
+ end
64
+
65
+ private
66
+
67
+ def missing_packages
68
+ pip = Legion::Python.venv_pip
69
+ output, status = Open3.capture2e(pip, 'list', '--format=json')
70
+ return Legion::Python::PACKAGES.dup unless status.success?
71
+
72
+ installed_names = ::JSON.parse(output).map { |p| p['name'].downcase.tr('-', '_') }
73
+
74
+ Legion::Python::PACKAGES.reject do |pkg|
75
+ installed_names.include?(pkg.downcase.tr('-', '_'))
76
+ end
77
+ rescue StandardError
78
+ Legion::Python::PACKAGES.dup
79
+ end
80
+
81
+ def venv_summary
82
+ python_bin = Legion::Python.venv_python
83
+ if File.executable?(python_bin)
84
+ version = `"#{python_bin}" --version 2>&1`.strip
85
+ "#{version} at #{Legion::Python::VENV_DIR}"
86
+ else
87
+ Legion::Python::VENV_DIR
88
+ end
89
+ rescue StandardError
90
+ Legion::Python::VENV_DIR
91
+ end
92
+
93
+ def pass_result(message)
94
+ Result.new(name: name, status: :pass, message: message)
95
+ end
96
+
97
+ def warn_result(message, prescription, auto_fixable: false)
98
+ Result.new(
99
+ name: name,
100
+ status: :warn,
101
+ message: message,
102
+ prescription: prescription,
103
+ auto_fixable: auto_fixable
104
+ )
105
+ end
106
+
107
+ def skip_result(message)
108
+ Result.new(name: name, status: :skip, message: message)
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -19,6 +19,7 @@ module Legion
19
19
  autoload :TlsCheck, 'legion/cli/doctor/tls_check'
20
20
  autoload :ApiBindCheck, 'legion/cli/doctor/api_bind_check'
21
21
  autoload :ModeCheck, 'legion/cli/doctor/mode_check'
22
+ autoload :PythonEnvCheck, 'legion/cli/doctor/python_env_check'
22
23
 
23
24
  def self.exit_on_failure?
24
25
  true
@@ -41,6 +42,7 @@ module Legion
41
42
  TlsCheck
42
43
  ApiBindCheck
43
44
  ModeCheck
45
+ PythonEnvCheck
44
46
  ].freeze
45
47
 
46
48
  # Weights: security > connectivity > convenience
@@ -55,6 +57,7 @@ module Legion
55
57
  'Bundle' => 1.5,
56
58
  'Config' => 1.0,
57
59
  'Extensions' => 1.0,
60
+ 'Python env' => 1.0,
58
61
  'PID files' => 0.5
59
62
  }.freeze
60
63
 
@@ -3,9 +3,11 @@
3
3
  require 'English'
4
4
  require 'json'
5
5
  require 'fileutils'
6
+ require 'open3'
6
7
  require 'thor'
7
8
  require 'rbconfig'
8
9
  require 'legion/cli/output'
10
+ require 'legion/python'
9
11
 
10
12
  module Legion
11
13
  module CLI
@@ -56,6 +58,10 @@ module Legion
56
58
  }
57
59
  }.freeze
58
60
 
61
+ PYTHON_PACKAGES = Legion::Python::PACKAGES
62
+ PYTHON_VENV_DIR = Legion::Python::VENV_DIR
63
+ PYTHON_MARKER = Legion::Python::MARKER
64
+
59
65
  SKILL_CONTENT = <<~MARKDOWN
60
66
  ---
61
67
  name: legion
@@ -146,6 +152,74 @@ module Legion
146
152
  install_pack(:channels)
147
153
  end
148
154
 
155
+ desc 'python', 'Set up Legion Python environment (venv + document/data packages)'
156
+ option :packages, type: :array, default: [], banner: 'PKG [PKG...]', desc: 'Additional pip packages to install'
157
+ option :rebuild, type: :boolean, default: false, desc: 'Destroy and recreate the venv from scratch'
158
+ def python # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
159
+ out = formatter
160
+ results = []
161
+
162
+ python3 = find_python3
163
+ unless python3
164
+ out.error('python3 not found. Install it with: brew install python')
165
+ exit 1
166
+ end
167
+
168
+ if options[:rebuild] && Dir.exist?(PYTHON_VENV_DIR)
169
+ out.header("Rebuilding Python venv at #{PYTHON_VENV_DIR}") unless options[:json]
170
+ FileUtils.rm_rf(PYTHON_VENV_DIR)
171
+ end
172
+
173
+ unless File.exist?("#{PYTHON_VENV_DIR}/pyvenv.cfg")
174
+ out.header("Creating Python venv at #{PYTHON_VENV_DIR}") unless options[:json]
175
+ FileUtils.mkdir_p(File.dirname(PYTHON_VENV_DIR))
176
+ unless system(python3, '-m', 'venv', PYTHON_VENV_DIR)
177
+ out.error('Failed to create Python venv')
178
+ exit 1
179
+ end
180
+ results << { action: 'created_venv', path: PYTHON_VENV_DIR }
181
+ end
182
+
183
+ pip = "#{PYTHON_VENV_DIR}/bin/pip"
184
+ unless File.executable?(pip)
185
+ out.error("pip not found at #{pip} — try: legionio setup python --rebuild")
186
+ exit 1
187
+ end
188
+
189
+ packages = PYTHON_PACKAGES + Array(options[:packages])
190
+ packages.uniq!
191
+
192
+ failed = false
193
+ packages.each do |pkg|
194
+ puts " Installing #{pkg}..." unless options[:json]
195
+ output, status = Open3.capture2e(pip, 'install', '--quiet', '--upgrade', pkg)
196
+ if status.success?
197
+ out.success(" #{pkg}") unless options[:json]
198
+ results << { package: pkg, status: 'installed' }
199
+ else
200
+ failed = true
201
+ out.error(" #{pkg} failed") unless options[:json]
202
+ results << { package: pkg, status: 'failed', error: output.strip.lines.last&.strip }
203
+ end
204
+ end
205
+
206
+ write_python_marker(python3, packages)
207
+
208
+ if options[:json]
209
+ out.json(venv: PYTHON_VENV_DIR, python: python_version(python3), results: results)
210
+ else
211
+ out.spacer
212
+ out.success("Python environment ready: #{PYTHON_VENV_DIR}/bin/python3")
213
+ out.spacer
214
+ puts " Interpreter: #{PYTHON_VENV_DIR}/bin/python3"
215
+ puts ' Env var: $LEGION_PYTHON'
216
+ puts ' Add packages: legionio setup python --packages <name> [<name>...]'
217
+ puts ' Rebuild venv: legionio setup python --rebuild'
218
+ end
219
+
220
+ exit 1 if failed
221
+ end
222
+
149
223
  desc 'packs', 'Show installed feature packs and available gems'
150
224
  def packs
151
225
  out = formatter
@@ -207,6 +281,36 @@ module Legion
207
281
 
208
282
  private
209
283
 
284
+ # -----------------------------------------------------------------------
285
+ # Python helpers
286
+ # -----------------------------------------------------------------------
287
+
288
+ def find_python3
289
+ Legion::Python.find_system_python3
290
+ end
291
+
292
+ def python_version(python3)
293
+ `"#{python3}" --version 2>&1`.strip
294
+ rescue StandardError
295
+ 'unknown'
296
+ end
297
+
298
+ def write_python_marker(python3, packages)
299
+ FileUtils.mkdir_p(File.dirname(PYTHON_MARKER))
300
+ File.write(PYTHON_MARKER, ::JSON.pretty_generate(
301
+ venv: PYTHON_VENV_DIR,
302
+ python: python_version(python3),
303
+ packages: packages,
304
+ updated_at: Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
305
+ ))
306
+ rescue Errno::EPERM, Errno::EACCES, Errno::ENOENT => e
307
+ Legion::Logging.warn("SetupCommand#write_python_marker: #{e.message}") if defined?(Legion::Logging)
308
+ end
309
+
310
+ # -----------------------------------------------------------------------
311
+ # Pack helpers
312
+ # -----------------------------------------------------------------------
313
+
210
314
  def install_pack(pack_name)
211
315
  pack = PACKS[pack_name]
212
316
  installed, missing = partition_gems(pack[:gems])
@@ -345,6 +449,10 @@ module Legion
345
449
  end
346
450
  end
347
451
 
452
+ # -----------------------------------------------------------------------
453
+ # MCP / editor platform helpers
454
+ # -----------------------------------------------------------------------
455
+
348
456
  def install_claude_mcp(installed)
349
457
  settings_path = File.expand_path('~/.claude/settings.json')
350
458
  existing = load_json_file(settings_path)
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Python
5
+ VENV_DIR = File.expand_path('~/.legionio/python').freeze
6
+ MARKER = File.expand_path('~/.legionio/.python-venv').freeze
7
+
8
+ PACKAGES = %w[
9
+ python-pptx
10
+ python-docx
11
+ openpyxl
12
+ pandas
13
+ pillow
14
+ requests
15
+ lxml
16
+ PyYAML
17
+ tabulate
18
+ markdown
19
+ ].freeze
20
+
21
+ SYSTEM_CANDIDATES = %w[
22
+ /opt/homebrew/bin/python3
23
+ /usr/local/bin/python3
24
+ /usr/bin/python3
25
+ ].freeze
26
+
27
+ module_function
28
+
29
+ def venv_exists?
30
+ File.exist?("#{VENV_DIR}/pyvenv.cfg")
31
+ end
32
+
33
+ def venv_python
34
+ "#{VENV_DIR}/bin/python3"
35
+ end
36
+
37
+ def venv_pip
38
+ "#{VENV_DIR}/bin/pip"
39
+ end
40
+
41
+ def venv_python_exists?
42
+ File.executable?(venv_python)
43
+ end
44
+
45
+ def venv_pip_exists?
46
+ File.executable?(venv_pip)
47
+ end
48
+
49
+ def interpreter
50
+ return venv_python if venv_python_exists?
51
+
52
+ find_system_python3 || 'python3'
53
+ end
54
+
55
+ def pip
56
+ return venv_pip if venv_pip_exists?
57
+
58
+ 'pip3'
59
+ end
60
+
61
+ def find_system_python3
62
+ path_python = `command -v python3 2>/dev/null`.strip
63
+ candidates = SYSTEM_CANDIDATES.dup
64
+ candidates.unshift(path_python) unless path_python.empty?
65
+ candidates.uniq.find { |p| File.executable?(p) }
66
+ end
67
+ end
68
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Legion
4
- VERSION = '1.7.34'
4
+ VERSION = '1.7.35'
5
5
  end
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.7.34
4
+ version: 1.7.35
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -651,6 +651,7 @@ files:
651
651
  - lib/legion/cli/doctor/mode_check.rb
652
652
  - lib/legion/cli/doctor/permissions_check.rb
653
653
  - lib/legion/cli/doctor/pid_check.rb
654
+ - lib/legion/cli/doctor/python_env_check.rb
654
655
  - lib/legion/cli/doctor/rabbitmq_check.rb
655
656
  - lib/legion/cli/doctor/result.rb
656
657
  - lib/legion/cli/doctor/ruby_version_check.rb
@@ -869,6 +870,7 @@ files:
869
870
  - lib/legion/process_role.rb
870
871
  - lib/legion/prompts.rb
871
872
  - lib/legion/provider.rb
873
+ - lib/legion/python.rb
872
874
  - lib/legion/readiness.rb
873
875
  - lib/legion/region.rb
874
876
  - lib/legion/region/failover.rb