Class: Legion::CLI::Setup

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

Constant Summary collapse

LEGION_MCP_ENTRY =
{
  'command' => 'legionio',
  'args'    => %w[mcp stdio]
}.freeze
PACKS =
{
  agentic:  {
    description: 'Full cognitive stack: core libs, agentic domains, AI providers, and operational extensions',
    gems:        %w[
      legion-apollo legion-gaia legion-llm legion-mcp legion-rbac
      lex-acp lex-adapter lex-agentic-affect lex-agentic-attention
      lex-agentic-defense lex-agentic-executive lex-agentic-homeostasis
      lex-agentic-imagination lex-agentic-inference lex-agentic-integration
      lex-agentic-language lex-agentic-learning lex-agentic-memory
      lex-agentic-self lex-agentic-social lex-apollo lex-audit lex-autofix
      lex-codegen lex-coldstart
      lex-conditioner lex-cost-scanner lex-dataset lex-detect
      lex-eval lex-exec lex-extinction lex-factory lex-finops
      lex-governance lex-kerberos lex-knowledge lex-llm
      lex-llm-anthropic lex-llm-azure-foundry lex-llm-bedrock
      lex-llm-gemini lex-llm-ledger lex-llm-mlx
      lex-llm-ollama lex-llm-openai lex-llm-vertex lex-llm-vllm
      lex-metering lex-mesh lex-microsoft_teams lex-mind-growth lex-node
      lex-onboard lex-pilot-infra-monitor
      lex-pilot-knowledge-assist lex-privatecore lex-prompt lex-react
      lex-swarm lex-swarm-github lex-synapse lex-telemetry lex-tick
      lex-transformer
    ]
  },
  llm:      {
    description: 'LLM routing and provider integration (no cognitive stack)',
    gems:        %w[
      legion-llm lex-llm lex-llm-anthropic lex-llm-azure-foundry
      lex-llm-bedrock lex-llm-gemini lex-llm-mlx
      lex-llm-ollama lex-llm-openai lex-llm-vertex lex-llm-vllm
    ]
  },
  gaia:     {
    description: 'Cognitive coordination engine + agentic extensions (GAIA stack)',
    gems:        %w[
      legion-gaia
      lex-agentic-affect lex-agentic-attention lex-agentic-defense
      lex-agentic-executive lex-agentic-homeostasis lex-agentic-inference
      lex-agentic-integration lex-agentic-language lex-agentic-learning
      lex-agentic-memory lex-agentic-self lex-agentic-social
      lex-synapse lex-mind-growth lex-tick
    ]
  },
  identity: {
    description: 'Identity and access management (RBAC + identity providers)',
    gems:        %w[
      legion-rbac lex-identity-system lex-identity-kerberos
    ]
  },
  channels: {
    description: 'Channel adapters for chat platforms',
    gems:        %w[lex-slack lex-microsoft_teams]
  }
}.freeze
PYTHON_PACKAGES =
Legion::Python::PACKAGES
PYTHON_VENV_DIR =
Legion::Python::VENV_DIR
PYTHON_MARKER =
Legion::Python::MARKER
SKILL_CONTENT =
<<~MARKDOWN
  ---
  name: legion
  description: Orchestrate LegionIO extensions and agents
  ---

  You have access to LegionIO MCP tools. When the user asks you to work with Legion:

  1. Use `legion.discover_tools` to find relevant capabilities
  2. Use `legion.do_action` for natural language task routing
  3. Use `legion.run_task` to execute specific extension functions
  4. Use `legion.list_peers` and `legion.ask_peer` for agent coordination
  5. Present results as a consolidated summary
MARKDOWN

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.exit_on_failure?Boolean

Returns:

  • (Boolean)


17
18
19
# File 'lib/legion/cli/setup_command.rb', line 17

def self.exit_on_failure?
  true
end

Instance Method Details

#agenticObject



161
162
163
# File 'lib/legion/cli/setup_command.rb', line 161

def agentic
  install_pack(:agentic)
end

#channelsObject



187
188
189
# File 'lib/legion/cli/setup_command.rb', line 187

def channels
  install_pack(:channels)
end

#claude_codeObject



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/legion/cli/setup_command.rb', line 105

def claude_code
  out = formatter
  installed = []

  install_claude_mcp(installed)
  install_claude_skill(installed)
  install_claude_hooks(installed)

  if options[:json]
    out.json(platform: 'claude-code', installed: installed)
  else
    out.spacer
    out.success("Legion configured for Claude Code (#{installed.size} item(s))")
    out.spacer
    puts "  Run '/legion' in Claude Code to use your LegionIO tools."
  end
end

#cursorObject



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/legion/cli/setup_command.rb', line 124

def cursor
  out = formatter
  path = File.join(Dir.pwd, '.cursor', 'mcp.json')
  installed = []

  write_mcp_servers_json(nil, path, installed)

  if options[:json]
    out.json(platform: 'cursor', installed: installed)
  else
    out.spacer
    out.success("Legion configured for Cursor (#{installed.size} item(s))")
    out.spacer
    puts "  MCP config written to: #{path}"
  end
end

#fleetObject



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/legion/cli/setup_command.rb', line 194

def fleet
  require 'legion/cli/fleet_setup'
  setup = Legion::CLI::FleetSetup.new(formatter: formatter, options: options)

  if options[:dry_run]
    gems = Legion::CLI::FleetSetup.fleet_gems
    installed, missing = gems.partition { |g| Gem::Specification.find_by_name(g) rescue nil } # rubocop:disable Style/RescueModifier
    if options[:json]
      formatter.json(to_install: missing, already_installed: installed)
    else
      formatter.header('Fleet Setup (dry run)')
      missing.each { |g| puts "  install  #{g}" }
      installed.each { |g| puts "  skip     #{g} (already installed)" }
    end
    return
  end

  case options[:phase]
  when 1
    result = setup.phase1_install
  when 2
    Connection.ensure_data
    result = setup.phase2_wire
    Connection.shutdown
  else
    result = setup.phase1_install
    if result[:success]
      formatter.spacer unless options[:json]
      formatter.warn('Phase 2 requires LegionIO restart to register extensions.') unless options[:json]
      formatter.warn('Run: legionio start && legionio setup fleet --phase 2') unless options[:json]
    end
  end

  formatter.json(result) if options[:json]
rescue SystemExit
  raise
rescue StandardError => e
  formatter.error("Fleet setup failed: #{e.message}")
  raise SystemExit, 1
end

#gaiaObject



175
176
177
# File 'lib/legion/cli/setup_command.rb', line 175

def gaia
  install_pack(:gaia)
end

#identityObject



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

def identity
  install_pack(:identity)
end

#llmObject



169
170
171
# File 'lib/legion/cli/setup_command.rb', line 169

def llm
  install_pack(:llm)
end

#packsObject



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/legion/cli/setup_command.rb', line 304

def packs
  out = formatter
  pack_statuses = PACKS.map do |name, pack|
    installed, missing = partition_gems(pack[:gems])
    { name: name, description: pack[:description],
      installed: installed.map { |g| { name: g, version: gem_version(g) } },
      missing: missing }
  end

  if options[:json]
    out.json(packs: pack_statuses)
  else
    out.header('Feature Packs')
    out.spacer
    pack_statuses.each do |ps|
      all_installed = ps[:missing].empty?
      icon = all_installed ? out.colorize('installed', :success) : out.colorize('not installed', :muted)
      puts "  #{out.colorize(ps[:name].to_s.ljust(12), :label)} #{icon}  #{ps[:description]}"
      ps[:installed].each do |g|
        puts "    #{out.colorize(g[:name], :success)} #{g[:version]}"
      end
      ps[:missing].each do |g|
        puts "    #{out.colorize(g, :muted)} (missing)"
      end
    end
    out.spacer
  end
end

#pythonObject

rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/legion/cli/setup_command.rb', line 238

def python # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
  out = formatter
  results = []

  python3 = find_python3
  unless python3
    out.error('python3 not found. Install it with: brew install python')
    exit 1
  end

  if options[:rebuild] && Dir.exist?(PYTHON_VENV_DIR)
    out.header("Rebuilding Python venv at #{PYTHON_VENV_DIR}") unless options[:json]
    FileUtils.rm_rf(PYTHON_VENV_DIR)
  end

  unless File.exist?("#{PYTHON_VENV_DIR}/pyvenv.cfg")
    out.header("Creating Python venv at #{PYTHON_VENV_DIR}") unless options[:json]
    FileUtils.mkdir_p(File.dirname(PYTHON_VENV_DIR))
    unless system(python3, '-m', 'venv', PYTHON_VENV_DIR)
      out.error('Failed to create Python venv')
      exit 1
    end
    results << { action: 'created_venv', path: PYTHON_VENV_DIR }
  end

  pip = "#{PYTHON_VENV_DIR}/bin/pip"
  unless File.executable?(pip)
    out.error("pip not found at #{pip} — try: legionio setup python --rebuild")
    exit 1
  end

  packages = PYTHON_PACKAGES + Array(options[:packages])
  packages.uniq!

  failed = false
  packages.each do |pkg|
    puts "  Installing #{pkg}..." unless options[:json]
    output, status = Open3.capture2e(pip, 'install', '--quiet', '--upgrade', pkg)
    if status.success?
      out.success("  #{pkg}") unless options[:json]
      results << { package: pkg, status: 'installed' }
    else
      failed = true
      out.error("  #{pkg} failed") unless options[:json]
      results << { package: pkg, status: 'failed', error: output.strip.lines.last&.strip }
    end
  end

  write_python_marker(python3, packages)

  if options[:json]
    out.json(venv: PYTHON_VENV_DIR, python: python_version(python3), results: results)
  else
    out.spacer
    out.success("Python environment ready: #{PYTHON_VENV_DIR}/bin/python3")
    out.spacer
    puts "  Interpreter:    #{PYTHON_VENV_DIR}/bin/python3"
    puts '  Env var:        $LEGION_PYTHON'
    puts '  Add packages:   legionio setup python --packages <name> [<name>...]'
    puts '  Rebuild venv:   legionio setup python --rebuild'
  end

  exit 1 if failed
end

#statusObject



334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/legion/cli/setup_command.rb', line 334

def status
  out = formatter
  platforms = check_all_platforms

  if options[:json]
    out.json(platforms: platforms)
  else
    out.header('Legion MCP Setup Status')
    out.spacer
    platforms.each do |p|
      icon = p[:configured] ? out.colorize('configured', :success) : out.colorize('not configured', :muted)
      puts "  #{out.colorize(p[:name].ljust(16), :label)} #{icon}"
      puts "    #{out.colorize(p[:path], :muted)}" if p[:path]
    end
    out.spacer
    configured_count = platforms.count { |p| p[:configured] }
    puts "  #{configured_count} of #{platforms.size} platform(s) configured"
  end
end

#vscodeObject



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/legion/cli/setup_command.rb', line 142

def vscode
  out = formatter
  path = File.join(Dir.pwd, '.vscode', 'mcp.json')
  installed = []

  write_vscode_mcp_json(nil, path, installed)

  if options[:json]
    out.json(platform: 'vscode', installed: installed)
  else
    out.spacer
    out.success("Legion configured for VS Code (#{installed.size} item(s))")
    out.spacer
    puts "  MCP config written to: #{path}"
  end
end