legionio 1.4.192 → 1.4.197

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: f5df6894cf4d95b284ae6941616de97dd340f587b7cdff3bacf4747e82dcbd59
4
- data.tar.gz: dae8d3572fa9a72eeb38a77c180b1c020df29d3db5183686143c3f94af13a1f6
3
+ metadata.gz: 547bcf88727783f2d13346201194840fac26054686d646f4c357bbd366302187
4
+ data.tar.gz: 90801baaf55c27542bee67621c343ef5aacb0801f245ba78291e19dc6c867c92
5
5
  SHA512:
6
- metadata.gz: 035d06ce47e12222cf7cd96a8bb0d2b2f4aef9db8b20d78068890d63fc6e5d97e984fa2403e4e29567361fe43c98d332b0b09c688c8358d20867e5e0e5ce3ed7
7
- data.tar.gz: 0fbec6e0c903c4d9831b7802e34ba4699e7a4e0cc76353b7cc2793fa36c80c89a311955dca920394f01c857e71b93cd2e28c19577c24467ceee97593d1c6fc5e
6
+ metadata.gz: 19a98825568321c1dc9fd1c78aa6d1b621aab3eb8d0e17e5fe7073192508a0e8f892276f82e7d7c45a7c952f07f645a8bfa670f9d4f3d3b0f0760fee2955c0fe
7
+ data.tar.gz: 3a9dd4466bf92e96b860f6b38e0976e1c5a48526b4bded1150847449e96745207baa452de506fb44dedda93eaac1f6bee9120ad3aa015a8a99bbc0db70cb9af1
@@ -0,0 +1,31 @@
1
+ name: Publish to Homebrew
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ trigger-homebrew:
9
+ runs-on: ubuntu-latest
10
+ if: startsWith(github.event.release.tag_name, 'v')
11
+ steps:
12
+ - name: Extract version from tag
13
+ id: version
14
+ run: |
15
+ TAG="${RELEASE_TAG}"
16
+ VERSION="${TAG#v}"
17
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
18
+ echo "Extracted version: $VERSION"
19
+ env:
20
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
21
+
22
+ - name: Trigger build-legion on homebrew-tap
23
+ run: |
24
+ gh api repos/LegionIO/homebrew-tap/dispatches \
25
+ -f event_type=build-legion \
26
+ -f "client_payload[legionio_version]=${LEGIONIO_VERSION}" \
27
+ -f "client_payload[ruby_version]=3.4.8" \
28
+ -f "client_payload[package_revision]=1"
29
+ env:
30
+ GH_TOKEN: ${{ secrets.HOMEBREW_DISPATCH_TOKEN }}
31
+ LEGIONIO_VERSION: ${{ steps.version.outputs.version }}
data/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Legion Changelog
2
2
 
3
+ ## [1.4.197] - 2026-03-24
4
+
5
+ ### Changed
6
+ - Add debug logging to 8 swallowed `rescue StandardError` blocks in chat tools and session store: ModelComparison, SystemStatus (fetch_health, fetch_ready), SessionStore (generate_summary, read_session_meta), SaveMemory (ingest_to_apollo), GenerateInsights (scheduling_status, llm_status)
7
+
8
+ ## [1.4.196] - 2026-03-24
9
+
10
+ ### Added
11
+ - LLM fallback in `legion do` command: when keyword matching (`find_by_intent`) returns no results, classifies intent via `Legion::LLM.ask` against the full Capability Registry catalog
12
+ - Graceful degradation: LLM path only activates when both `Legion::LLM` and `Catalog::Registry` are loaded; errors fall through silently
13
+
14
+ ## [1.4.195] - 2026-03-24
15
+
16
+ ### Added
17
+ - `legion do "TEXT"` CLI command: natural language intent router that matches free-text to Capability Registry entries and dispatches via daemon API or in-process Ingress
18
+ - `DoCommand` module with two resolution paths: daemon HTTP dispatch (like `dream`) and in-process `Registry.find_by_intent` + `Ingress.run` fallback
19
+
20
+ ## [1.4.194] - 2026-03-24
21
+
22
+ ### Added
23
+ - `--lite` flag on `legion start` command: sets `LEGION_MODE=lite` and `LEGION_LOCAL=true` env vars, assigns `:lite` process role
24
+ - `:lite` process role in `ProcessRole::ROLES`: all subsystems enabled except `crypt: false` (Vault not needed in lite mode)
25
+ - `Service#lite_mode?` checks `LEGION_MODE` env var and `settings[:mode]`
26
+ - `setup_local_mode` handles lite mode: sets dev flag, loads Transport::Local, loads mock_vault if Crypt is defined
27
+
28
+ ## [1.4.193] - 2026-03-24
29
+
30
+ ### Added
31
+ - `legion mind-growth` CLI subcommand with 10 commands: status, propose, approve, reject, build, proposals, profile, health, report, history
32
+ - Delegates to `Legion::Extensions::MindGrowth::Client` (lex-mind-growth extension)
33
+ - Guards with `require_mind_growth!` — raises `CLI::Error` when extension is not loaded
34
+ - Supports `--json` and `--no-color` class options on all subcommands
35
+
3
36
  ## [1.4.192] - 2026-03-24
4
37
 
5
38
  ### Fixed
@@ -123,7 +123,8 @@ module Legion
123
123
 
124
124
  def apollo_data_connected?
125
125
  defined?(Legion::Data) && Legion::Data.respond_to?(:connection) && !Legion::Data.connection.nil?
126
- rescue StandardError
126
+ rescue StandardError => e
127
+ Legion::Logging.debug("Apollo#apollo_data_connected? check failed: #{e.message}") if defined?(Legion::Logging)
127
128
  false
128
129
  end
129
130
 
@@ -46,7 +46,8 @@ module Legion
46
46
  module CostHelpers
47
47
  def metering_available?
48
48
  defined?(Legion::Data) && Legion::Data.respond_to?(:connection) && !Legion::Data.connection.nil?
49
- rescue StandardError
49
+ rescue StandardError => e
50
+ Legion::Logging.debug("CostHelpers#metering_available? check failed: #{e.message}") if defined?(Legion::Logging)
50
51
  false
51
52
  end
52
53
 
@@ -90,7 +90,8 @@ module Legion
90
90
  first_msg = user_messages.first[:content].to_s.strip
91
91
  first_msg = "#{first_msg[0..120]}..." if first_msg.length > 120
92
92
  first_msg
93
- rescue StandardError
93
+ rescue StandardError => e
94
+ Legion::Logging.debug("SessionStore#generate_summary failed: #{e.message}") if defined?(Legion::Logging)
94
95
  nil
95
96
  end
96
97
 
@@ -102,7 +103,8 @@ module Legion
102
103
  summary: data[:summary],
103
104
  model: data[:model]
104
105
  }
105
- rescue StandardError
106
+ rescue StandardError => e
107
+ Legion::Logging.debug("SessionStore#read_session_meta failed: #{e.message}") if defined?(Legion::Logging)
106
108
  { message_count: nil, summary: nil, model: nil }
107
109
  end
108
110
  end
@@ -166,7 +166,8 @@ module Legion
166
166
  end
167
167
  result[:batch] = Legion::LLM::Batch.status if defined?(Legion::LLM::Batch)
168
168
  result.empty? ? nil : result
169
- rescue StandardError
169
+ rescue StandardError => e
170
+ Legion::Logging.debug("GenerateInsights#scheduling_status failed: #{e.message}") if defined?(Legion::Logging)
170
171
  nil
171
172
  end
172
173
 
@@ -181,7 +182,8 @@ module Legion
181
182
  result[:shadow_evals] = s[:total_evaluations]
182
183
  end
183
184
  result.empty? ? nil : result
184
- rescue StandardError
185
+ rescue StandardError => e
186
+ Legion::Logging.debug("GenerateInsights#llm_status failed: #{e.message}") if defined?(Legion::Logging)
185
187
  nil
186
188
  end
187
189
 
@@ -40,7 +40,8 @@ module Legion
40
40
  Legion::LLM::CostTracker::DEFAULT_PRICING.transform_values do |v|
41
41
  { input: v[:input], output: v[:output] }
42
42
  end
43
- rescue StandardError
43
+ rescue StandardError => e
44
+ Legion::Logging.debug("ModelComparison#cost_tracker_pricing failed: #{e.message}") if defined?(Legion::Logging)
44
45
  {}
45
46
  end
46
47
 
@@ -54,7 +54,8 @@ module Legion
54
54
  return nil if data[:error]
55
55
 
56
56
  'Also ingested into Apollo knowledge graph.'
57
- rescue StandardError
57
+ rescue StandardError => e
58
+ Legion::Logging.debug("SaveMemory#ingest_to_apollo failed: #{e.message}") if defined?(Legion::Logging)
58
59
  nil
59
60
  end
60
61
 
@@ -39,7 +39,8 @@ module Legion
39
39
  api_get('/api/health')
40
40
  rescue Errno::ECONNREFUSED
41
41
  raise
42
- rescue StandardError
42
+ rescue StandardError => e
43
+ Legion::Logging.debug("SystemStatus#fetch_health failed: #{e.message}") if defined?(Legion::Logging)
43
44
  nil
44
45
  end
45
46
 
@@ -47,7 +48,8 @@ module Legion
47
48
  api_get('/api/ready')
48
49
  rescue Errno::ECONNREFUSED
49
50
  raise
50
- rescue StandardError
51
+ rescue StandardError => e
52
+ Legion::Logging.debug("SystemStatus#fetch_ready failed: #{e.message}") if defined?(Legion::Logging)
51
53
  nil
52
54
  end
53
55
 
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module CLI
5
+ module DoCommand
6
+ class << self
7
+ def run(intent, formatter, options)
8
+ if intent.strip.empty?
9
+ formatter.error('Usage: legion do "describe what you want"')
10
+ raise SystemExit, 1
11
+ end
12
+
13
+ formatter.detail("Routing intent: #{intent}")
14
+
15
+ result = try_daemon(intent, options) || try_in_process(intent) || try_llm_classify(intent)
16
+
17
+ if result.nil?
18
+ formatter.error('No matching capability found')
19
+ formatter.detail('Try: legion lex list (to see available extensions)')
20
+ raise SystemExit, 1
21
+ end
22
+
23
+ display_result(result, formatter, options)
24
+ end
25
+
26
+ private
27
+
28
+ def try_daemon(intent, options)
29
+ require 'net/http'
30
+ require 'json'
31
+
32
+ port = daemon_port(options)
33
+ uri = URI("http://localhost:#{port}/api/tasks")
34
+ body = ::JSON.generate({
35
+ runner_class: resolve_runner_class(intent) || return,
36
+ function: resolve_function(intent) || return,
37
+ payload: { intent: intent },
38
+ source: 'cli:do',
39
+ check_subtask: false,
40
+ generate_task: true
41
+ })
42
+
43
+ http = Net::HTTP.new(uri.host, uri.port)
44
+ http.open_timeout = 3
45
+ http.read_timeout = 30
46
+ request = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
47
+ request.body = body
48
+
49
+ response = http.request(request)
50
+ ::JSON.parse(response.body, symbolize_names: true)
51
+ rescue Errno::ECONNREFUSED, Net::OpenTimeout
52
+ nil
53
+ end
54
+
55
+ def try_in_process(intent)
56
+ return nil unless defined?(Legion::Extensions::Catalog::Registry)
57
+
58
+ matches = Legion::Extensions::Catalog::Registry.find_by_intent(intent)
59
+ return nil if matches.empty?
60
+
61
+ best = matches.first
62
+ runner_class = build_runner_class(best.extension, best.runner)
63
+
64
+ if defined?(Legion::Ingress)
65
+ Legion::Ingress.run(
66
+ payload: { intent: intent },
67
+ runner_class: runner_class,
68
+ function: best.function,
69
+ source: 'cli:do'
70
+ )
71
+ else
72
+ { matched: best.name, runner_class: runner_class, function: best.function,
73
+ status: 'resolved', note: 'Daemon not running; cannot execute. Start with: legion start' }
74
+ end
75
+ end
76
+
77
+ def try_llm_classify(intent)
78
+ return nil unless defined?(Legion::Extensions::Catalog::Registry) && defined?(Legion::LLM)
79
+
80
+ caps = Legion::Extensions::Catalog::Registry.capabilities
81
+ return nil if caps.empty?
82
+
83
+ catalog = caps.map { |c| "#{c.name}: #{c.description || "#{c.extension} #{c.runner}##{c.function}"}" }
84
+ prompt = "Given these capabilities:\n#{catalog.join("\n")}\n\n" \
85
+ "Which capability best matches this intent: \"#{intent}\"?\n" \
86
+ 'Reply with ONLY the capability name (e.g., lex-consul:health_check:run). ' \
87
+ 'If none match, reply NONE.'
88
+
89
+ response = Legion::LLM.ask(
90
+ message: prompt,
91
+ caller: { extension: 'legionio', tool: 'do_command', tier: 'cli' }
92
+ )
93
+ chosen = response.is_a?(Hash) ? response[:response].to_s.strip : response.to_s.strip
94
+ return nil if chosen.empty? || chosen.upcase == 'NONE'
95
+
96
+ cap = Legion::Extensions::Catalog::Registry.find(name: chosen)
97
+ return nil unless cap
98
+
99
+ runner_class = build_runner_class(cap.extension, cap.runner)
100
+ { matched: cap.name, runner_class: runner_class, function: cap.function,
101
+ status: 'resolved', source: 'llm',
102
+ note: 'Daemon not running; cannot execute. Start with: legion start' }
103
+ rescue StandardError => e
104
+ Legion::Logging.debug("DoCommand#try_llm_classify failed: #{e.message}") if defined?(Legion::Logging)
105
+ nil
106
+ end
107
+
108
+ def resolve_runner_class(intent)
109
+ return nil unless defined?(Legion::Extensions::Catalog::Registry)
110
+
111
+ matches = Legion::Extensions::Catalog::Registry.find_by_intent(intent)
112
+ return nil if matches.empty?
113
+
114
+ build_runner_class(matches.first.extension, matches.first.runner)
115
+ end
116
+
117
+ def resolve_function(intent)
118
+ return nil unless defined?(Legion::Extensions::Catalog::Registry)
119
+
120
+ matches = Legion::Extensions::Catalog::Registry.find_by_intent(intent)
121
+ return nil if matches.empty?
122
+
123
+ matches.first.function
124
+ end
125
+
126
+ def build_runner_class(extension, runner)
127
+ ext_part = extension.delete_prefix('lex-').split(/[-_]/).map(&:capitalize).join
128
+ "Legion::Extensions::#{ext_part}::Runners::#{runner}"
129
+ end
130
+
131
+ def daemon_port(options)
132
+ options[:http_port] || begin
133
+ require 'legion/settings'
134
+ Legion::Settings.load unless Legion::Settings.loaded?
135
+ Legion::Settings.dig(:api, :port) || 4567
136
+ rescue StandardError
137
+ 4567
138
+ end
139
+ end
140
+
141
+ def display_result(result, formatter, options)
142
+ if options[:json]
143
+ formatter.json(result)
144
+ elsif result.is_a?(Hash) && result[:error]
145
+ formatter.error(result.dig(:error, :message) || result[:error].to_s)
146
+ elsif result.is_a?(Hash) && result[:data]
147
+ formatter.success('Task dispatched')
148
+ formatter.detail(result[:data])
149
+ elsif result.is_a?(Hash) && result[:matched]
150
+ formatter.success("Matched: #{result[:matched]}")
151
+ formatter.detail(result.except(:matched))
152
+ else
153
+ formatter.success('Done')
154
+ formatter.detail(result)
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'thor'
5
+
6
+ module Legion
7
+ module CLI
8
+ class MindGrowth < Thor
9
+ def self.exit_on_failure?
10
+ true
11
+ end
12
+
13
+ class_option :json, type: :boolean, default: false, desc: 'Output as JSON'
14
+ class_option :no_color, type: :boolean, default: false, desc: 'Disable color output'
15
+ class_option :verbose, type: :boolean, default: false, aliases: ['-V'], desc: 'Verbose logging'
16
+
17
+ desc 'status', 'Show mind-growth cycle status'
18
+ def status
19
+ require_mind_growth!
20
+ result = mind_growth_client.growth_status
21
+ out = formatter
22
+ if options[:json]
23
+ out.json(result)
24
+ else
25
+ out.header('Mind-Growth Status')
26
+ out.spacer
27
+ out.detail(result)
28
+ end
29
+ end
30
+
31
+ desc 'propose', 'Propose a new cognitive concept'
32
+ option :category, type: :string, desc: 'Cognitive category'
33
+ option :description, type: :string, desc: 'Concept description'
34
+ option :name, type: :string, desc: 'Concept name'
35
+ def propose
36
+ require_mind_growth!
37
+ result = mind_growth_client.propose_concept(
38
+ category: options[:category]&.to_sym,
39
+ description: options[:description],
40
+ name: options[:name]
41
+ )
42
+ out = formatter
43
+ if options[:json]
44
+ out.json(result)
45
+ elsif result[:success]
46
+ out.success("Proposal created: #{result.dig(:proposal, :id)}")
47
+ else
48
+ out.warn("Proposal failed: #{result[:error]}")
49
+ end
50
+ end
51
+
52
+ desc 'approve ID', 'Approve a proposal'
53
+ def approve(proposal_id)
54
+ require_mind_growth!
55
+ result = mind_growth_client.evaluate_proposal(proposal_id: proposal_id)
56
+ out = formatter
57
+ if options[:json]
58
+ out.json(result)
59
+ elsif result[:success]
60
+ status_label = result[:approved] ? 'approved' : 'rejected'
61
+ out.success("Proposal #{proposal_id[0, 8]} #{status_label}")
62
+ else
63
+ out.warn("Evaluation failed: #{result[:error]}")
64
+ end
65
+ end
66
+
67
+ desc 'reject ID', 'Reject a proposal'
68
+ map 'reject' => :reject_proposal
69
+ option :reason, type: :string, desc: 'Rejection reason'
70
+ def reject_proposal(proposal_id)
71
+ require_mind_growth!
72
+ proposal = Legion::Extensions::MindGrowth::Runners::Proposer.get_proposal_object(proposal_id)
73
+ out = formatter
74
+ if proposal.nil?
75
+ out.warn("Proposal #{proposal_id[0, 8]} not found")
76
+ return
77
+ end
78
+ proposal.transition!(:rejected)
79
+ if options[:json]
80
+ out.json({ success: true, proposal_id: proposal_id, status: 'rejected',
81
+ reason: options[:reason] })
82
+ else
83
+ out.success("Proposal #{proposal_id[0, 8]} rejected")
84
+ end
85
+ rescue ArgumentError => e
86
+ formatter.warn("Cannot reject: #{e.message}")
87
+ end
88
+
89
+ desc 'build ID', 'Force-build an approved proposal'
90
+ def build(proposal_id)
91
+ require_mind_growth!
92
+ result = mind_growth_client.build_extension(proposal_id: proposal_id)
93
+ out = formatter
94
+ if options[:json]
95
+ out.json(result)
96
+ elsif result[:success]
97
+ out.success("Build pipeline started for #{proposal_id[0, 8]}")
98
+ out.detail(result[:pipeline]) if result[:pipeline]
99
+ else
100
+ out.warn("Build failed: #{result[:error]}")
101
+ end
102
+ end
103
+
104
+ desc 'proposals', 'List proposals'
105
+ option :status, type: :string, desc: 'Filter by status'
106
+ option :limit, type: :numeric, default: 20, desc: 'Max results'
107
+ def proposals
108
+ require_mind_growth!
109
+ result = mind_growth_client.list_proposals(
110
+ status: options[:status]&.to_sym,
111
+ limit: options[:limit]
112
+ )
113
+ out = formatter
114
+ if options[:json]
115
+ out.json(result)
116
+ else
117
+ rows = (result[:proposals] || []).map do |p|
118
+ [p[:id].to_s[0, 8], p[:name].to_s, p[:category].to_s,
119
+ p[:status].to_s, p[:created_at].to_s]
120
+ end
121
+ if rows.empty?
122
+ out.warn('No proposals found')
123
+ else
124
+ out.table(%w[id name category status created_at], rows)
125
+ end
126
+ end
127
+ end
128
+
129
+ desc 'profile', 'Show cognitive architecture profile'
130
+ def profile
131
+ require_mind_growth!
132
+ result = mind_growth_client.cognitive_profile
133
+ out = formatter
134
+ if options[:json]
135
+ out.json(result)
136
+ else
137
+ out.header('Cognitive Architecture Profile')
138
+ out.spacer
139
+ out.detail({ total_extensions: result[:total_extensions],
140
+ overall_coverage: result[:overall_coverage] })
141
+ out.spacer
142
+ coverage = result[:model_coverage] || {}
143
+ rows = coverage.map { |model, data| [model.to_s, data[:coverage].to_s, data[:missing].to_s] }
144
+ out.table(%w[model coverage missing], rows) unless rows.empty?
145
+ end
146
+ end
147
+
148
+ desc 'health', 'Show extension health and fitness scores'
149
+ def health
150
+ require_mind_growth!
151
+ result = mind_growth_client.validate_fitness(extensions: [])
152
+ out = formatter
153
+ if options[:json]
154
+ out.json(result)
155
+ else
156
+ out.header('Extension Fitness')
157
+ out.spacer
158
+ ranked = result[:ranked] || []
159
+ if ranked.empty?
160
+ out.warn('No extensions to score')
161
+ else
162
+ rows = ranked.map { |e| [e[:name].to_s, format('%.3f', e[:fitness].to_f)] }
163
+ out.table(%w[extension fitness], rows)
164
+ end
165
+ end
166
+ end
167
+
168
+ desc 'report', 'Generate retrospective report'
169
+ def report
170
+ require_mind_growth!
171
+ result = mind_growth_client.session_report
172
+ out = formatter
173
+ if options[:json]
174
+ out.json(result)
175
+ else
176
+ out.header('Mind-Growth Report')
177
+ out.spacer
178
+ out.detail(result)
179
+ end
180
+ end
181
+
182
+ desc 'history', 'Show recent proposal history'
183
+ option :limit, type: :numeric, default: 50, desc: 'Max results'
184
+ def history
185
+ require_mind_growth!
186
+ result = mind_growth_client.list_proposals(limit: options[:limit])
187
+ out = formatter
188
+ if options[:json]
189
+ out.json(result)
190
+ else
191
+ rows = (result[:proposals] || []).map do |p|
192
+ [p[:id].to_s[0, 8], p[:name].to_s, p[:category].to_s,
193
+ p[:status].to_s, p[:created_at].to_s]
194
+ end
195
+ if rows.empty?
196
+ out.warn('No proposals found')
197
+ else
198
+ out.table(%w[id name category status created_at], rows)
199
+ end
200
+ end
201
+ end
202
+
203
+ no_commands do
204
+ def formatter
205
+ @formatter ||= Output::Formatter.new(
206
+ json: options[:json],
207
+ color: !options[:no_color]
208
+ )
209
+ end
210
+
211
+ def require_mind_growth!
212
+ return if defined?(Legion::Extensions::MindGrowth::Client)
213
+
214
+ raise CLI::Error, 'lex-mind-growth extension is not loaded. Install and enable it first.'
215
+ end
216
+
217
+ def mind_growth_client
218
+ @mind_growth_client ||= Legion::Extensions::MindGrowth::Client.new
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
@@ -5,6 +5,11 @@ module Legion
5
5
  module Start
6
6
  class << self
7
7
  def run(options)
8
+ if options[:lite]
9
+ ENV['LEGION_MODE'] = 'lite'
10
+ ENV['LEGION_LOCAL'] = 'true'
11
+ end
12
+
8
13
  log_level = options[:log_level] || 'info'
9
14
 
10
15
  require 'legion'
@@ -16,6 +21,7 @@ module Legion
16
21
  api = options.fetch(:api, true)
17
22
  service_opts = { log_level: log_level, api: api }
18
23
  service_opts[:http_port] = options[:http_port] if options[:http_port]
24
+ service_opts[:role] = :lite if options[:lite]
19
25
  Legion.instance_variable_set(:@service, Legion::Service.new(**service_opts))
20
26
  Legion::Logging.info("Started Legion v#{Legion::VERSION}")
21
27
 
data/lib/legion/cli.rb CHANGED
@@ -24,7 +24,8 @@ module Legion
24
24
  autoload :Commit, 'legion/cli/commit_command'
25
25
  autoload :Pr, 'legion/cli/pr_command'
26
26
  autoload :Review, 'legion/cli/review_command'
27
- autoload :Memory, 'legion/cli/memory_command'
27
+ autoload :Memory, 'legion/cli/memory_command'
28
+ autoload :MindGrowth, 'legion/cli/mind_growth_command'
28
29
  autoload :Plan, 'legion/cli/plan_command'
29
30
  autoload :Swarm, 'legion/cli/swarm_command'
30
31
  autoload :Gaia, 'legion/cli/gaia_command'
@@ -54,6 +55,7 @@ module Legion
54
55
  autoload :Tty, 'legion/cli/tty_command'
55
56
  autoload :ObserveCommand, 'legion/cli/observe_command'
56
57
  autoload :Payroll, 'legion/cli/payroll_command'
58
+ autoload :DoCommand, 'legion/cli/do_command'
57
59
  autoload :Interactive, 'legion/cli/interactive'
58
60
  autoload :Docs, 'legion/cli/docs_command'
59
61
  autoload :Failover, 'legion/cli/failover_command'
@@ -122,6 +124,7 @@ module Legion
122
124
  option :log_level, type: :string, default: 'info', desc: 'Log level (debug, info, warn, error)'
123
125
  option :api, type: :boolean, default: true, desc: 'Start the HTTP API server'
124
126
  option :http_port, type: :numeric, desc: 'HTTP API port (overrides settings)'
127
+ option :lite, type: :boolean, default: false, desc: 'Start in lite mode (no external services)'
125
128
  def start
126
129
  Legion::CLI::Start.run(options)
127
130
  end
@@ -218,6 +221,9 @@ module Legion
218
221
  desc 'memory SUBCOMMAND', 'Persistent project memory across sessions'
219
222
  subcommand 'memory', Legion::CLI::Memory
220
223
 
224
+ desc 'mind-growth SUBCOMMAND', 'Autonomous cognitive architecture expansion'
225
+ subcommand 'mind-growth', Legion::CLI::MindGrowth
226
+
221
227
  desc 'plan', 'Start plan mode (read-only exploration, no writes)'
222
228
  subcommand 'plan', Legion::CLI::Plan
223
229
 
@@ -325,6 +331,21 @@ module Legion
325
331
  Legion::CLI::Chat.start(['prompt', text.join(' ')] + ARGV.select { |a| a.start_with?('--') })
326
332
  end
327
333
 
334
+ desc 'do TEXT', 'Route a natural language intent to the right extension'
335
+ long_desc <<~DESC
336
+ Describe what you want in plain English. Legion routes to the best
337
+ matching extension and runner automatically.
338
+
339
+ Examples:
340
+ legion do "check consul health"
341
+ legion do "list running tasks"
342
+ legion do "review the latest PR"
343
+ DESC
344
+ def do_action(*text)
345
+ Legion::CLI::DoCommand.run(text.join(' '), formatter, options)
346
+ end
347
+ map 'do' => :do_action
348
+
328
349
  desc 'dream', 'Trigger a dream cycle on the running daemon'
329
350
  option :wait, type: :boolean, default: false, desc: 'Wait for dream cycle to complete'
330
351
  def dream
@@ -6,7 +6,8 @@ module Legion
6
6
  full: { transport: true, cache: true, data: true, extensions: true, api: true, llm: true, gaia: true, crypt: true, supervision: true },
7
7
  api: { transport: true, cache: true, data: true, extensions: false, api: true, llm: false, gaia: false, crypt: true, supervision: false },
8
8
  worker: { transport: true, cache: true, data: true, extensions: true, api: false, llm: true, gaia: true, crypt: true, supervision: true },
9
- router: { transport: true, cache: true, data: false, extensions: true, api: false, llm: false, gaia: false, crypt: true, supervision: false }
9
+ router: { transport: true, cache: true, data: false, extensions: true, api: false, llm: false, gaia: false, crypt: true, supervision: false },
10
+ lite: { transport: true, cache: true, data: true, extensions: true, api: true, llm: true, gaia: true, crypt: false, supervision: true }
10
11
  }.freeze
11
12
 
12
13
  def self.resolve(role_name)
@@ -97,6 +97,14 @@ module Legion
97
97
  end
98
98
 
99
99
  def setup_local_mode
100
+ if lite_mode?
101
+ Legion::Logging.info 'Starting in lite mode (zero infrastructure)'
102
+ Legion::Settings[:dev] = true
103
+ require 'legion/transport/local'
104
+ require 'legion/crypt/mock_vault' if defined?(Legion::Crypt)
105
+ return
106
+ end
107
+
100
108
  return unless local_mode?
101
109
 
102
110
  Legion::Logging.info 'Starting in local development mode'
@@ -111,6 +119,11 @@ module Legion
111
119
  Legion::Settings[:local_mode] == true
112
120
  end
113
121
 
122
+ def lite_mode?
123
+ ENV['LEGION_MODE'] == 'lite' ||
124
+ Legion::Settings[:mode].to_s == 'lite'
125
+ end
126
+
114
127
  def setup_data
115
128
  Legion::Logging.info 'Setting up Legion::Data'
116
129
  require 'legion/data'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Legion
4
- VERSION = '1.4.192'
4
+ VERSION = '1.4.197'
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.4.192
4
+ version: 1.4.197
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -361,6 +361,7 @@ files:
361
361
  - ".github/workflow-templates/eval-gate.yml"
362
362
  - ".github/workflows/ci-cd.yml"
363
363
  - ".github/workflows/ci.yml"
364
+ - ".github/workflows/publish-homebrew.yml"
364
365
  - ".gitignore"
365
366
  - ".rubocop.yml"
366
367
  - CHANGELOG.md
@@ -548,6 +549,7 @@ files:
548
549
  - lib/legion/cli/dashboard_command.rb
549
550
  - lib/legion/cli/dataset_command.rb
550
551
  - lib/legion/cli/detect_command.rb
552
+ - lib/legion/cli/do_command.rb
551
553
  - lib/legion/cli/docs_command.rb
552
554
  - lib/legion/cli/doctor.rb
553
555
  - lib/legion/cli/doctor/bundle_check.rb
@@ -628,6 +630,7 @@ files:
628
630
  - lib/legion/cli/marketplace_command.rb
629
631
  - lib/legion/cli/mcp_command.rb
630
632
  - lib/legion/cli/memory_command.rb
633
+ - lib/legion/cli/mind_growth_command.rb
631
634
  - lib/legion/cli/notebook_command.rb
632
635
  - lib/legion/cli/observe_command.rb
633
636
  - lib/legion/cli/openapi_command.rb