Class: RailsAiContext::Generators::InstallGenerator
- Inherits:
-
Rails::Generators::Base
- Object
- Rails::Generators::Base
- RailsAiContext::Generators::InstallGenerator
- Defined in:
- lib/generators/rails_ai_context/install/install_generator.rb
Constant Summary collapse
- AI_TOOLS =
{ "1" => { key: :claude, name: "Claude Code", files: "CLAUDE.md + .claude/rules/", format: :claude }, "2" => { key: :cursor, name: "Cursor", files: ".cursor/rules/", format: :cursor }, "3" => { key: :copilot, name: "GitHub Copilot", files: ".github/copilot-instructions.md + .github/instructions/", format: :copilot }, "4" => { key: :opencode, name: "OpenCode", files: "AGENTS.md", format: :opencode }, "5" => { key: :codex, name: "Codex CLI", files: "AGENTS.md + .codex/config.toml", format: :codex } }.freeze
- FORMAT_PATHS =
Files/dirs generated per AI tool format — used for cleanup on tool removal. MCP config files are NOT listed here — they use merge-safe removal via McpConfigGenerator.remove to preserve other servers’ entries.
{ claude: %w[CLAUDE.md .claude/rules], cursor: %w[.cursor/rules], copilot: %w[.github/copilot-instructions.md .github/instructions], opencode: %w[AGENTS.md app/models/AGENTS.md app/controllers/AGENTS.md], codex: %w[AGENTS.md app/models/AGENTS.md app/controllers/AGENTS.md] }.freeze
- CONFIG_SECTIONS =
All config sections with their marker comment and content. Each section is identified by its marker (e.g., “── AI Tools ──”). On re-install, only sections NOT already present are appended.
{ "AI Tools" => <<~SECTION, "Introspection" => <<~SECTION, "Models & Filtering" => <<~SECTION, "MCP Server" => <<~SECTION, "File Size Limits" => <<~SECTION, "Extensibility" => <<~SECTION, "Security" => <<~SECTION, "Search" => <<~SECTION, "Frontend" => <<~SECTION # ── Frontend Framework Detection ───────────────────────────────── # Auto-detected from package.json, config/vite.json, etc. Override only if needed. # config.frontend_paths = ["app/frontend", "../web-client"] SECTION }.freeze
Instance Method Summary collapse
- #add_to_gitignore ⇒ Object
- #cleanup_removed_tools ⇒ Object
- #create_initializer ⇒ Object
- #create_mcp_config ⇒ Object
-
#create_yaml_config ⇒ Object
no_tasks.
- #generate_context_files ⇒ Object
- #select_ai_tools ⇒ Object
- #select_tool_mode ⇒ Object
- #show_instructions ⇒ Object
Instance Method Details
#add_to_gitignore ⇒ Object
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 |
# File 'lib/generators/rails_ai_context/install/install_generator.rb', line 468 def add_to_gitignore gitignore = Rails.root.join(".gitignore") return unless File.exist?(gitignore) content = File.read(gitignore) append = [] append << ".ai-context.json" unless content.include?(".ai-context.json") if append.any? File.open(gitignore, "a") do |f| f.puts "" f.puts "# rails-ai-context (JSON cache — markdown files should be committed)" append.each { |line| f.puts line } end say "Updated .gitignore", :green end end |
#cleanup_removed_tools ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/generators/rails_ai_context/install/install_generator.rb', line 60 def cleanup_removed_tools @previous_formats = read_previous_ai_tools return unless @previous_formats&.any? removed = @previous_formats - @selected_formats return if removed.empty? say "" say "These AI tools were removed from your selection:", :yellow removed.each_with_index do |fmt, idx| tool = AI_TOOLS.values.find { |t| t[:format] == fmt } say " #{idx + 1}. #{tool[:name]} (#{tool[:files]})" if tool end say "" say "Remove their generated files?", :yellow say " y — remove all listed above" say " n — keep all (default)" say " 1,2 — remove only specific ones by number" say "" input = ask("Enter choice:").strip.downcase return if input.empty? || input == "n" || input == "no" to_remove = if input == "y" || input == "yes" || input == "a" removed else nums = input.split(/[\s,]+/).filter_map { |n| n.to_i - 1 } nums.filter_map { |i| removed[i] if i >= 0 && i < removed.size } end return if to_remove.empty? # Collect paths still needed by remaining tools to avoid deleting shared files kept_paths = @selected_formats.flat_map { |f| FORMAT_PATHS[f] || [] }.to_set to_remove.each do |fmt| tool = AI_TOOLS.values.find { |t| t[:format] == fmt } # Remove context files (skip if another selected tool still needs them) paths = FORMAT_PATHS[fmt] || [] paths.each do |rel_path| next if kept_paths.include?(rel_path) full = Rails.root.join(rel_path) if File.directory?(full) FileUtils.rm_rf(full) say " Removed #{rel_path}/", :red elsif File.exist?(full) FileUtils.rm_f(full) say " Removed #{rel_path}", :red end end # Merge-safe MCP config cleanup — removes only the rails-ai-context entry cleaned = RailsAiContext::McpConfigGenerator.remove(tools: [ fmt ], output_dir: Rails.root.to_s) cleaned.each { |f| say " Removed MCP entry from #{Pathname.new(f).relative_path_from(Rails.root)}", :red } say " ✓ #{tool[:name]} files removed", :green if tool end end |
#create_initializer ⇒ Object
277 278 279 280 281 282 283 284 285 286 |
# File 'lib/generators/rails_ai_context/install/install_generator.rb', line 277 def create_initializer initializer_path = "config/initializers/rails_ai_context.rb" full_path = Rails.root.join(initializer_path) if File.exist?(full_path) update_existing_initializer(full_path) else create_new_initializer(initializer_path) end end |
#create_mcp_config ⇒ Object
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/generators/rails_ai_context/install/install_generator.rb', line 141 def create_mcp_config generator = RailsAiContext::McpConfigGenerator.new( tools: @selected_formats, output_dir: Rails.root.to_s, standalone: false, tool_mode: @tool_mode ) result = generator.call result[:written].each do |f| rel = Pathname.new(f).relative_path_from(Rails.root) say "Created/Updated #{rel}", :green end result[:skipped].each do |f| rel = Pathname.new(f).relative_path_from(Rails.root) say "#{rel} unchanged — skipped", :yellow end if @tool_mode == :cli say "Skipped MCP config files (CLI-only mode)", :yellow end end |
#create_yaml_config ⇒ Object
no_tasks
456 457 458 459 460 461 462 463 464 465 466 |
# File 'lib/generators/rails_ai_context/install/install_generator.rb', line 456 def create_yaml_config yaml_path = Rails.root.join(".rails-ai-context.yml") content = { "ai_tools" => @selected_formats.map(&:to_s), "tool_mode" => @tool_mode.to_s } require "yaml" File.write(yaml_path, YAML.dump(content)) say "Created .rails-ai-context.yml (standalone config)", :green end |
#generate_context_files ⇒ Object
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 |
# File 'lib/generators/rails_ai_context/install/install_generator.rb', line 486 def generate_context_files say "" say "Generating AI context files...", :yellow unless Rails.application say " Skipped (Rails app not fully loaded). Run `rails ai:context` after install.", :yellow return end require "rails_ai_context" # One-time v5.0.0 legacy UI-pattern files cleanup prompt RailsAiContext::LegacyCleanup.prompt_legacy_files( @selected_formats, root: Rails.root ) @selected_formats.each do |fmt| begin result = RailsAiContext.generate_context(format: fmt) (result[:written] || []).each { |f| say " ✅ #{f}", :green } (result[:skipped] || []).each { |f| say " ⏭️ #{f} (unchanged)", :yellow } rescue => e say " ❌ #{fmt}: #{e.}", :red end end end |
#select_ai_tools ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/generators/rails_ai_context/install/install_generator.rb', line 31 def select_ai_tools say "" say "Which AI tools do you use? (select all that apply)", :yellow say "" AI_TOOLS.each do |num, info| say " #{num}. #{info[:name].ljust(16)} → #{info[:files]}" end say " a. All of the above" say "" input = ask("Enter numbers separated by commas (e.g. 1,2) or 'a' for all:").strip.downcase @selected_formats = if input == "a" || input == "all" AI_TOOLS.values.map { |t| t[:format] } else nums = input.split(/[\s,]+/) nums.filter_map { |n| AI_TOOLS[n]&.dig(:format) } end if @selected_formats.empty? say "No tools selected — defaulting to all.", :yellow @selected_formats = AI_TOOLS.values.map { |t| t[:format] } end selected_names = AI_TOOLS.values.select { |t| @selected_formats.include?(t[:format]) }.map { |t| t[:name] } say "" say "Selected: #{selected_names.join(', ')}", :green end |
#select_tool_mode ⇒ Object
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/generators/rails_ai_context/install/install_generator.rb', line 122 def select_tool_mode say "" say "Do you also want MCP server support?", :yellow say "" say " 1. Yes — MCP primary + CLI fallback (generates per-tool MCP config files)" say " 2. No — CLI only (no server needed)" say "" input = ask("Enter number (default: 1):").strip @tool_mode = case input when "2" then :cli else :mcp end mode_label = @tool_mode == :mcp ? "MCP + CLI fallback" : "CLI only" say "Selected: #{mode_label}", :green end |
#show_instructions ⇒ Object
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 |
# File 'lib/generators/rails_ai_context/install/install_generator.rb', line 513 def show_instructions say "" say "=" * 50, :cyan say " rails-ai-context installed!", :cyan say "=" * 50, :cyan say "" say "Your setup:", :yellow AI_TOOLS.each_value do |info| next unless @selected_formats.include?(info[:format]) say " ✅ #{info[:name].ljust(16)} → #{info[:files]}" end say "" say "Commands:", :yellow say " rails ai:context # Regenerate context files" tool_count = RailsAiContext::Server.builtin_tools.size say " rails 'ai:tool[schema]' # Run any of the #{tool_count} tools from CLI" if @tool_mode == :mcp say " rails ai:serve # Start MCP server (#{tool_count} live tools)" end say " rails ai:doctor # Check AI readiness" say " rails ai:inspect # Print introspection summary" say "" if @tool_mode == :mcp say "MCP auto-discovery:", :yellow say " Each AI tool gets its own config file — auto-detected on project open." say " No manual config needed." else say "CLI tools:", :yellow say " AI agents can run `rails 'ai:tool[schema]' table=users` directly." say " No MCP server needed — tools work from the terminal." end say "" say "To add more AI tools later:", :yellow say " rails ai:context:cursor # Generate for Cursor" say " rails ai:context:copilot # Generate for Copilot" say " rails generate rails_ai_context:install # Re-run to pick tools" say "" say "Standalone (no Gemfile needed):", :yellow say " gem install rails-ai-context" say " rails-ai-context init # interactive setup" say " rails-ai-context serve # start MCP server" say "" say "Commit context files and MCP config files so your team benefits!", :green end |