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: 'Cognitive stack: agentic domains, AI providers, and coordination',
    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-coldstart
      lex-conditioner lex-detect lex-extinction lex-kerberos lex-knowledge
      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 lex-metering lex-mesh
      lex-microsoft_teams lex-mind-growth lex-node lex-privatecore
      lex-synapse lex-telemetry lex-tick
    ]
  },
  llm:       {
    description: 'LLM routing and provider integration (no cognitive stack)',
    gems:        %w[
      legion-llm legion-mcp 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 lex-kerberos
    ]
  },
  developer: {
    description: 'Developer tooling and CI/CD integrations',
    gems:        %w[
      lex-developer lex-dynatrace lex-eval lex-exec lex-github
      lex-http lex-jfrog lex-skill-superpowers lex-ssh
    ]
  },
  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



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

def agentic
  install_pack(:agentic)
end

#channelsObject



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

def channels
  install_pack(:channels)
end

#claude_codeObject



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

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



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

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



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
234
# File 'lib/legion/cli/setup_command.rb', line 195

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



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

def gaia
  install_pack(:gaia)
end

#identityObject



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

def identity
  install_pack(:identity)
end

#llmObject



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

def llm
  install_pack(:llm)
end

#packsObject



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
332
# File 'lib/legion/cli/setup_command.rb', line 305

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



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
302
# File 'lib/legion/cli/setup_command.rb', line 239

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



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

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



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

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