Class: Legion::CLI::Main

Inherits:
Thor
  • Object
show all
Defined in:
lib/legion/cli.rb

Constant Summary collapse

LEGION_GEMS =
%w[
  legion-transport legion-cache legion-crypt legion-data
  legion-json legion-logging legion-settings
  legion-llm legion-gaia legion-mcp legion-rbac
  legion-tty legion-ffi
].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.exit_on_failure?Boolean

Returns:

  • (Boolean)


88
89
90
# File 'lib/legion/cli.rb', line 88

def self.exit_on_failure?
  true
end

.normalize_help_args(given_args) ⇒ Object



109
110
111
112
113
114
115
116
117
118
# File 'lib/legion/cli.rb', line 109

def self.normalize_help_args(given_args)
  args = Array(given_args).dup
  return args unless args.length == 2
  return args unless %w[--help -h].include?(args.last)

  command = args.first
  return args if command.start_with?('-') || command == 'help'

  ['help', command]
end

.start(given_args = ARGV, config = {}) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/legion/cli.rb', line 92

def self.start(given_args = ARGV, config = {})
  super(normalize_help_args(given_args), config)
rescue Legion::CLI::Error => e
  Legion::Logging.error("CLI::Main.start CLI error: #{e.message}") if defined?(Legion::Logging)
  formatter = Output::Formatter.new(json: given_args.include?('--json'), color: !given_args.include?('--no-color'))
  ErrorHandler.format_error(e, formatter)
  ErrorForwarder.forward_error(e, command: given_args.join(' '))
  exit(1)
rescue StandardError => e
  Legion::Logging.error("CLI::Main.start unexpected error: #{e.message}") if defined?(Legion::Logging)
  wrapped = ErrorHandler.wrap(e)
  formatter = Output::Formatter.new(json: given_args.include?('--json'), color: !given_args.include?('--no-color'))
  ErrorHandler.format_error(wrapped, formatter)
  ErrorForwarder.forward_error(e, command: given_args.join(' '))
  exit(1)
end

Instance Method Details

#ask(*text) ⇒ Object



326
327
328
# File 'lib/legion/cli.rb', line 326

def ask(*text)
  Legion::CLI::Chat.start(['prompt', text.join(' ')] + ARGV.select { |a| a.start_with?('--') })
end

#checkObject



225
226
227
228
229
230
231
232
# File 'lib/legion/cli.rb', line 225

def check
  exit_code = if options[:privacy]
                Legion::CLI::Check.run_privacy(formatter, options)
              else
                Legion::CLI::Check.run(formatter, options)
              end
  exit(exit_code) if exit_code != 0
end

#do_action(*text) ⇒ Object



340
341
342
# File 'lib/legion/cli.rb', line 340

def do_action(*text)
  Legion::CLI::DoCommand.run(text.join(' '), formatter, options)
end

#dreamObject



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/legion/cli.rb', line 347

def dream
  out = formatter
  require 'net/http'
  require 'json'
  port = api_port
  uri = URI("http://localhost:#{port}/api/tasks")
  body = ::JSON.generate({
                           runner_class:  'Legion::Extensions::Dream::Runners::DreamCycle',
                           function:      'execute_dream_cycle',
                           async:         !options[:wait],
                           check_subtask: false,
                           generate_task: false
                         })

  http = Net::HTTP.new(uri.host, uri.port)
  http.open_timeout = 5
  http.read_timeout = options[:wait] ? 300 : 5
  request = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
  request.body = body

  response = http.request(request)
  parsed = ::JSON.parse(response.body, symbolize_names: true)

  if options[:json]
    out.json(parsed)
  elsif response.is_a?(Net::HTTPSuccess)
    out.success('Dream cycle triggered on daemon')
    out.detail(parsed[:data] || parsed) if parsed[:data]
  else
    out.error("Dream cycle failed: #{parsed.dig(:error, :message) || response.code}")
  end
rescue Net::ReadTimeout => e
  Legion::Logging.debug("CLI#dream read timeout (expected for background tasks): #{e.message}") if defined?(Legion::Logging)
  out.success('Dream cycle triggered on daemon (running in background)')
rescue Errno::ECONNREFUSED => e
  Legion::Logging.warn("CLI#dream daemon not running: #{e.message}") if defined?(Legion::Logging)
  out.error(format('Daemon not running (connection refused on port %d)', port))
  raise SystemExit, 1
end

#startObject



181
182
183
# File 'lib/legion/cli.rb', line 181

def start
  Legion::CLI::Start.run(options)
end

#statusObject



209
210
211
# File 'lib/legion/cli.rb', line 209

def status
  Legion::CLI::Status.run(formatter, options)
end

#stopObject



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/legion/cli.rb', line 188

def stop
  out = formatter
  pidfile = options[:pidfile] || find_pidfile
  unless pidfile && File.exist?(pidfile)
    out.error('No PID file found. Is Legion running?')
    raise SystemExit, 1
  end

  pid = File.read(pidfile).to_i
  sig = options[:signal].upcase
  Process.kill(sig, pid)
  out.success("Sent #{sig} to Legion process #{pid}")
rescue Errno::ESRCH
  out.warn("Process #{pid} not found (already stopped?)")
  FileUtils.rm_f(pidfile)
rescue Errno::EPERM
  out.error("Permission denied sending signal to process #{pid}")
  raise SystemExit, 1
end

#treeObject



320
321
322
# File 'lib/legion/cli.rb', line 320

def tree
  legion_print_command_tree(self.class, ::File.basename($PROGRAM_NAME), '')
end

#versionObject



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/legion/cli.rb', line 135

def version
  out = formatter
  lexs = discovered_lexs
  if options[:json]
    payload = { version: Legion::VERSION, ruby: RUBY_VERSION, platform: RUBY_PLATFORM,
                components: installed_components, extensions: lexs.size }
    payload[:extension_versions] = lex_versions(lexs) if options[:full]
    out.json(payload)
  else
    out.banner(version: Legion::VERSION)
    out.spacer
    out.detail({ ruby: RUBY_VERSION, platform: RUBY_PLATFORM })
    out.spacer

    installed = installed_components
    out.header('Components')
    installed.each do |name, ver|
      puts "  #{out.colorize(name.to_s.ljust(20), :label)} #{ver}"
    end

    out.spacer
    puts "  #{out.colorize("#{lexs.size} extension(s)", :accent)} installed"

    if options[:full] && lexs.any?
      out.spacer
      out.header('Extensions')
      lex_versions(lexs).each do |name, ver|
        puts "  #{out.colorize(name.ljust(20), :label)} #{ver}"
      end
    end
  end
end