ruby_llm-agents 0.5.0 → 1.0.0

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.
Files changed (190) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +189 -31
  3. data/app/controllers/ruby_llm/agents/agents_controller.rb +136 -16
  4. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +29 -9
  5. data/app/controllers/ruby_llm/agents/workflows_controller.rb +355 -0
  6. data/app/helpers/ruby_llm/agents/application_helper.rb +25 -0
  7. data/app/models/ruby_llm/agents/execution.rb +3 -0
  8. data/app/models/ruby_llm/agents/tenant_budget.rb +58 -15
  9. data/app/services/ruby_llm/agents/agent_registry.rb +51 -12
  10. data/app/views/layouts/ruby_llm/agents/application.html.erb +2 -29
  11. data/app/views/ruby_llm/agents/agents/_agent.html.erb +13 -1
  12. data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +235 -0
  13. data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +70 -0
  14. data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +152 -0
  15. data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +63 -0
  16. data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +108 -0
  17. data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +91 -0
  18. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +1 -1
  19. data/app/views/ruby_llm/agents/agents/index.html.erb +74 -9
  20. data/app/views/ruby_llm/agents/agents/show.html.erb +18 -378
  21. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +269 -15
  22. data/app/views/ruby_llm/agents/executions/show.html.erb +16 -0
  23. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +93 -0
  24. data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +236 -0
  25. data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +76 -0
  26. data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +74 -0
  27. data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +108 -0
  28. data/app/views/ruby_llm/agents/workflows/show.html.erb +442 -0
  29. data/config/routes.rb +1 -0
  30. data/lib/generators/ruby_llm_agents/agent_generator.rb +56 -7
  31. data/lib/generators/ruby_llm_agents/background_remover_generator.rb +110 -0
  32. data/lib/generators/ruby_llm_agents/embedder_generator.rb +107 -0
  33. data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +115 -0
  34. data/lib/generators/ruby_llm_agents/image_editor_generator.rb +108 -0
  35. data/lib/generators/ruby_llm_agents/image_generator_generator.rb +116 -0
  36. data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +178 -0
  37. data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +109 -0
  38. data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +103 -0
  39. data/lib/generators/ruby_llm_agents/image_variator_generator.rb +102 -0
  40. data/lib/generators/ruby_llm_agents/install_generator.rb +76 -4
  41. data/lib/generators/ruby_llm_agents/restructure_generator.rb +292 -0
  42. data/lib/generators/ruby_llm_agents/speaker_generator.rb +121 -0
  43. data/lib/generators/ruby_llm_agents/templates/add_execution_type_migration.rb.tt +8 -0
  44. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +99 -84
  45. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +42 -40
  46. data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +26 -0
  47. data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +50 -0
  48. data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +26 -0
  49. data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +20 -0
  50. data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +38 -0
  51. data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +139 -0
  52. data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +21 -0
  53. data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +20 -0
  54. data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +20 -0
  55. data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +49 -0
  56. data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +53 -0
  57. data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +44 -0
  58. data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +41 -0
  59. data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +45 -0
  60. data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +35 -0
  61. data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +47 -0
  62. data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +50 -0
  63. data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +44 -0
  64. data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +38 -0
  65. data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +33 -0
  66. data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +228 -0
  67. data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +131 -0
  68. data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +255 -0
  69. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +120 -0
  70. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +102 -0
  71. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +282 -0
  72. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +228 -0
  73. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +120 -0
  74. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +110 -0
  75. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +120 -0
  76. data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +212 -0
  77. data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +227 -0
  78. data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +251 -0
  79. data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +300 -0
  80. data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +56 -0
  81. data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +51 -0
  82. data/lib/generators/ruby_llm_agents/transcriber_generator.rb +107 -0
  83. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +152 -1
  84. data/lib/ruby_llm/agents/audio/speaker.rb +553 -0
  85. data/lib/ruby_llm/agents/audio/transcriber.rb +669 -0
  86. data/lib/ruby_llm/agents/base_agent.rb +675 -0
  87. data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +181 -0
  88. data/lib/ruby_llm/agents/core/base/moderation_execution.rb +274 -0
  89. data/lib/ruby_llm/agents/core/base.rb +135 -0
  90. data/lib/ruby_llm/agents/core/configuration.rb +981 -0
  91. data/lib/ruby_llm/agents/core/errors.rb +150 -0
  92. data/lib/ruby_llm/agents/{instrumentation.rb → core/instrumentation.rb} +22 -1
  93. data/lib/ruby_llm/agents/core/llm_tenant.rb +358 -0
  94. data/lib/ruby_llm/agents/{version.rb → core/version.rb} +1 -1
  95. data/lib/ruby_llm/agents/dsl/base.rb +110 -0
  96. data/lib/ruby_llm/agents/dsl/caching.rb +142 -0
  97. data/lib/ruby_llm/agents/dsl/reliability.rb +307 -0
  98. data/lib/ruby_llm/agents/dsl.rb +41 -0
  99. data/lib/ruby_llm/agents/image/analyzer/dsl.rb +130 -0
  100. data/lib/ruby_llm/agents/image/analyzer/execution.rb +402 -0
  101. data/lib/ruby_llm/agents/image/analyzer.rb +90 -0
  102. data/lib/ruby_llm/agents/image/background_remover/dsl.rb +154 -0
  103. data/lib/ruby_llm/agents/image/background_remover/execution.rb +240 -0
  104. data/lib/ruby_llm/agents/image/background_remover.rb +89 -0
  105. data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +91 -0
  106. data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +165 -0
  107. data/lib/ruby_llm/agents/image/editor/dsl.rb +56 -0
  108. data/lib/ruby_llm/agents/image/editor/execution.rb +207 -0
  109. data/lib/ruby_llm/agents/image/editor.rb +92 -0
  110. data/lib/ruby_llm/agents/image/generator/active_storage_support.rb +127 -0
  111. data/lib/ruby_llm/agents/image/generator/content_policy.rb +95 -0
  112. data/lib/ruby_llm/agents/image/generator/pricing.rb +353 -0
  113. data/lib/ruby_llm/agents/image/generator/templates.rb +124 -0
  114. data/lib/ruby_llm/agents/image/generator.rb +455 -0
  115. data/lib/ruby_llm/agents/image/pipeline/dsl.rb +213 -0
  116. data/lib/ruby_llm/agents/image/pipeline/execution.rb +382 -0
  117. data/lib/ruby_llm/agents/image/pipeline.rb +97 -0
  118. data/lib/ruby_llm/agents/image/transformer/dsl.rb +148 -0
  119. data/lib/ruby_llm/agents/image/transformer/execution.rb +223 -0
  120. data/lib/ruby_llm/agents/image/transformer.rb +95 -0
  121. data/lib/ruby_llm/agents/image/upscaler/dsl.rb +83 -0
  122. data/lib/ruby_llm/agents/image/upscaler/execution.rb +219 -0
  123. data/lib/ruby_llm/agents/image/upscaler.rb +81 -0
  124. data/lib/ruby_llm/agents/image/variator/dsl.rb +62 -0
  125. data/lib/ruby_llm/agents/image/variator/execution.rb +189 -0
  126. data/lib/ruby_llm/agents/image/variator.rb +80 -0
  127. data/lib/ruby_llm/agents/{alert_manager.rb → infrastructure/alert_manager.rb} +17 -22
  128. data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +145 -0
  129. data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +149 -0
  130. data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +68 -0
  131. data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +279 -0
  132. data/lib/ruby_llm/agents/infrastructure/budget_tracker.rb +275 -0
  133. data/lib/ruby_llm/agents/{execution_logger_job.rb → infrastructure/execution_logger_job.rb} +17 -1
  134. data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/executor.rb +2 -1
  135. data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/retry_strategy.rb +9 -3
  136. data/lib/ruby_llm/agents/{reliability.rb → infrastructure/reliability.rb} +11 -21
  137. data/lib/ruby_llm/agents/pipeline/builder.rb +215 -0
  138. data/lib/ruby_llm/agents/pipeline/context.rb +255 -0
  139. data/lib/ruby_llm/agents/pipeline/executor.rb +86 -0
  140. data/lib/ruby_llm/agents/pipeline/middleware/base.rb +124 -0
  141. data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +95 -0
  142. data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +171 -0
  143. data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +415 -0
  144. data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +276 -0
  145. data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +196 -0
  146. data/lib/ruby_llm/agents/pipeline.rb +68 -0
  147. data/lib/ruby_llm/agents/{engine.rb → rails/engine.rb} +79 -11
  148. data/lib/ruby_llm/agents/results/background_removal_result.rb +286 -0
  149. data/lib/ruby_llm/agents/{result.rb → results/base.rb} +73 -1
  150. data/lib/ruby_llm/agents/results/embedding_result.rb +243 -0
  151. data/lib/ruby_llm/agents/results/image_analysis_result.rb +314 -0
  152. data/lib/ruby_llm/agents/results/image_edit_result.rb +250 -0
  153. data/lib/ruby_llm/agents/results/image_generation_result.rb +346 -0
  154. data/lib/ruby_llm/agents/results/image_pipeline_result.rb +399 -0
  155. data/lib/ruby_llm/agents/results/image_transform_result.rb +251 -0
  156. data/lib/ruby_llm/agents/results/image_upscale_result.rb +255 -0
  157. data/lib/ruby_llm/agents/results/image_variation_result.rb +237 -0
  158. data/lib/ruby_llm/agents/results/moderation_result.rb +158 -0
  159. data/lib/ruby_llm/agents/results/speech_result.rb +338 -0
  160. data/lib/ruby_llm/agents/results/transcription_result.rb +408 -0
  161. data/lib/ruby_llm/agents/text/embedder.rb +444 -0
  162. data/lib/ruby_llm/agents/text/moderator.rb +237 -0
  163. data/lib/ruby_llm/agents/workflow/async.rb +220 -0
  164. data/lib/ruby_llm/agents/workflow/async_executor.rb +156 -0
  165. data/lib/ruby_llm/agents/{workflow.rb → workflow/orchestrator.rb} +6 -5
  166. data/lib/ruby_llm/agents/workflow/parallel.rb +34 -17
  167. data/lib/ruby_llm/agents/workflow/thread_pool.rb +185 -0
  168. data/lib/ruby_llm/agents.rb +86 -20
  169. metadata +172 -34
  170. data/lib/ruby_llm/agents/base/caching.rb +0 -40
  171. data/lib/ruby_llm/agents/base/cost_calculation.rb +0 -105
  172. data/lib/ruby_llm/agents/base/dsl.rb +0 -324
  173. data/lib/ruby_llm/agents/base/execution.rb +0 -366
  174. data/lib/ruby_llm/agents/base/reliability_dsl.rb +0 -82
  175. data/lib/ruby_llm/agents/base/reliability_execution.rb +0 -136
  176. data/lib/ruby_llm/agents/base/response_building.rb +0 -86
  177. data/lib/ruby_llm/agents/base/tool_tracking.rb +0 -57
  178. data/lib/ruby_llm/agents/base.rb +0 -210
  179. data/lib/ruby_llm/agents/budget_tracker.rb +0 -733
  180. data/lib/ruby_llm/agents/configuration.rb +0 -394
  181. /data/lib/ruby_llm/agents/{deprecations.rb → core/deprecations.rb} +0 -0
  182. /data/lib/ruby_llm/agents/{inflections.rb → core/inflections.rb} +0 -0
  183. /data/lib/ruby_llm/agents/{resolved_config.rb → core/resolved_config.rb} +0 -0
  184. /data/lib/ruby_llm/agents/{attempt_tracker.rb → infrastructure/attempt_tracker.rb} +0 -0
  185. /data/lib/ruby_llm/agents/{cache_helper.rb → infrastructure/cache_helper.rb} +0 -0
  186. /data/lib/ruby_llm/agents/{circuit_breaker.rb → infrastructure/circuit_breaker.rb} +0 -0
  187. /data/lib/ruby_llm/agents/{redactor.rb → infrastructure/redactor.rb} +0 -0
  188. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/breaker_manager.rb +0 -0
  189. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/execution_constraints.rb +0 -0
  190. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/fallback_routing.rb +0 -0
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module RubyLlmAgents
6
+ # Transcriber generator for creating new transcribers
7
+ #
8
+ # Usage:
9
+ # rails generate ruby_llm_agents:transcriber Meeting
10
+ # rails generate ruby_llm_agents:transcriber Meeting --model gpt-4o-transcribe
11
+ # rails generate ruby_llm_agents:transcriber Meeting --language es
12
+ # rails generate ruby_llm_agents:transcriber Meeting --root=ai
13
+ #
14
+ # This will create:
15
+ # - app/{root}/audio/transcribers/meeting_transcriber.rb
16
+ #
17
+ class TranscriberGenerator < ::Rails::Generators::NamedBase
18
+ source_root File.expand_path("templates", __dir__)
19
+
20
+ class_option :model, type: :string, default: "whisper-1",
21
+ desc: "The transcription model to use"
22
+ class_option :language, type: :string, default: nil,
23
+ desc: "Language code (e.g., 'en', 'es')"
24
+ class_option :output_format, type: :string, default: "text",
25
+ desc: "Output format (text, srt, vtt, json)"
26
+ class_option :cache, type: :string, default: nil,
27
+ desc: "Cache TTL (e.g., '30.days')"
28
+ class_option :root,
29
+ type: :string,
30
+ default: nil,
31
+ desc: "Root directory name (default: uses config or 'llm')"
32
+ class_option :namespace,
33
+ type: :string,
34
+ default: nil,
35
+ desc: "Root namespace (default: camelized root or config)"
36
+
37
+ def ensure_base_class_and_skill_file
38
+ @root_namespace = root_namespace
39
+ @audio_namespace = "#{root_namespace}::Audio"
40
+ transcribers_dir = "app/#{root_directory}/audio/transcribers"
41
+
42
+ # Create directory if needed
43
+ empty_directory transcribers_dir
44
+
45
+ # Create base class if it doesn't exist
46
+ base_class_path = "#{transcribers_dir}/application_transcriber.rb"
47
+ unless File.exist?(File.join(destination_root, base_class_path))
48
+ template "application_transcriber.rb.tt", base_class_path
49
+ end
50
+
51
+ # Create skill file if it doesn't exist
52
+ skill_file_path = "#{transcribers_dir}/TRANSCRIBERS.md"
53
+ unless File.exist?(File.join(destination_root, skill_file_path))
54
+ template "skills/TRANSCRIBERS.md.tt", skill_file_path
55
+ end
56
+ end
57
+
58
+ def create_transcriber_file
59
+ # Support nested paths: "interview/meeting" -> "app/{root}/audio/transcribers/interview/meeting_transcriber.rb"
60
+ @root_namespace = root_namespace
61
+ @audio_namespace = "#{root_namespace}::Audio"
62
+ transcriber_path = name.underscore
63
+ template "transcriber.rb.tt", "app/#{root_directory}/audio/transcribers/#{transcriber_path}_transcriber.rb"
64
+ end
65
+
66
+ def show_usage
67
+ # Build full class name from path
68
+ transcriber_class_name = name.split("/").map(&:camelize).join("::")
69
+ full_class_name = "#{root_namespace}::Audio::#{transcriber_class_name}Transcriber"
70
+ say ""
71
+ say "Transcriber #{full_class_name} created!", :green
72
+ say ""
73
+ say "Usage:"
74
+ say " # From file path"
75
+ say " #{full_class_name}.call(audio: \"recording.mp3\")"
76
+ say ""
77
+ say " # From URL"
78
+ say " #{full_class_name}.call(audio: \"https://example.com/audio.mp3\")"
79
+ say ""
80
+ say " # Get subtitles"
81
+ say " result = #{full_class_name}.call(audio: \"video.mp4\")"
82
+ say " result.srt # SRT format"
83
+ say " result.vtt # VTT format"
84
+ say ""
85
+ end
86
+
87
+ private
88
+
89
+ def root_directory
90
+ @root_directory ||= options[:root] || RubyLLM::Agents.configuration.root_directory
91
+ end
92
+
93
+ def root_namespace
94
+ @root_namespace ||= options[:namespace] || camelize(root_directory)
95
+ end
96
+
97
+ def camelize(str)
98
+ # Handle special cases for common abbreviations
99
+ return "AI" if str.downcase == "ai"
100
+ return "ML" if str.downcase == "ml"
101
+ return "LLM" if str.downcase == "llm"
102
+
103
+ # Standard camelization
104
+ str.split(/[-_]/).map(&:capitalize).join
105
+ end
106
+ end
107
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "rails/generators"
4
4
  require "rails/generators/active_record"
5
+ require "fileutils"
5
6
 
6
7
  module RubyLlmAgents
7
8
  # Upgrade generator for ruby_llm-agents
@@ -133,12 +134,62 @@ module RubyLlmAgents
133
134
  )
134
135
  end
135
136
 
137
+ def create_add_execution_type_migration
138
+ # Check if columns already exist
139
+ if column_exists?(:ruby_llm_agents_executions, :execution_type)
140
+ say_status :skip, "execution_type column already exists", :yellow
141
+ return
142
+ end
143
+
144
+ migration_template(
145
+ "add_execution_type_migration.rb.tt",
146
+ File.join(db_migrate_path, "add_execution_type_to_ruby_llm_agents_executions.rb")
147
+ )
148
+ end
149
+
150
+ def migrate_agents_directory
151
+ root_dir = RubyLLM::Agents.configuration.root_directory
152
+ namespace = RubyLLM::Agents.configuration.root_namespace
153
+ migrate_directory("agents", "#{root_dir}/agents", namespace)
154
+ end
155
+
156
+ def migrate_tools_directory
157
+ root_dir = RubyLLM::Agents.configuration.root_directory
158
+ namespace = RubyLLM::Agents.configuration.root_namespace
159
+ migrate_directory("tools", "#{root_dir}/tools", namespace)
160
+ end
161
+
136
162
  def show_post_upgrade_message
137
163
  say ""
138
- say "RubyLLM::Agents upgrade migration created!", :green
164
+ say "RubyLLM::Agents upgrade complete!", :green
139
165
  say ""
140
166
  say "Next steps:"
141
167
  say " 1. Run migrations: rails db:migrate"
168
+ say " 2. Update class references in your controllers, views, and tests"
169
+ say " 3. Run your test suite to find any broken references"
170
+ say ""
171
+ end
172
+
173
+ def show_migration_summary
174
+ namespace = RubyLLM::Agents.configuration.root_namespace
175
+ root_dir = RubyLLM::Agents.configuration.root_directory
176
+
177
+ return unless @agents_migrated || @tools_migrated
178
+
179
+ say ""
180
+ say "=" * 60
181
+ say " File Migration Summary", :green
182
+ say "=" * 60
183
+ say ""
184
+ say "Your agents and tools have been migrated to the new structure:"
185
+ say ""
186
+ say " app/agents/ → app/#{root_dir}/agents/" if @agents_migrated
187
+ say " app/tools/ → app/#{root_dir}/tools/" if @tools_migrated
188
+ say ""
189
+ say "Classes are now namespaced under #{namespace}::"
190
+ say ""
191
+ say " Before: GeneralAgent.call(...)"
192
+ say " After: #{namespace}::GeneralAgent.call(...)"
142
193
  say ""
143
194
  end
144
195
 
@@ -159,5 +210,105 @@ module RubyLlmAgents
159
210
  rescue StandardError
160
211
  false
161
212
  end
213
+
214
+ def migrate_directory(old_dir, new_dir, namespace)
215
+ source = Rails.root.join("app", old_dir)
216
+ destination = Rails.root.join("app", new_dir)
217
+
218
+ # Skip if source doesn't exist
219
+ unless File.directory?(source)
220
+ say_status :skip, "app/#{old_dir}/ does not exist", :yellow
221
+ return
222
+ end
223
+
224
+ # Skip if source and destination are the same
225
+ if source.to_s == destination.to_s
226
+ say_status :skip, "app/#{old_dir}/ is already at destination", :yellow
227
+ return
228
+ end
229
+
230
+ files_to_migrate = Dir.glob("#{source}/**/*.rb")
231
+ if files_to_migrate.empty?
232
+ say_status :skip, "app/#{old_dir}/ has no Ruby files to migrate", :yellow
233
+ return
234
+ end
235
+
236
+ if options[:pretend]
237
+ say_status :preview, "Would move #{files_to_migrate.size} files from app/#{old_dir}/ → app/#{new_dir}/", :yellow
238
+ files_to_migrate.each do |file|
239
+ relative = file.sub("#{source}/", "")
240
+ say_status :would_move, relative, :cyan
241
+ end
242
+ return
243
+ end
244
+
245
+ # Create destination directory
246
+ FileUtils.mkdir_p(destination)
247
+
248
+ # Track conflicts and migrated files
249
+ conflicts = []
250
+ migrated = []
251
+
252
+ files_to_migrate.each do |file|
253
+ relative_path = file.sub("#{source}/", "")
254
+ dest_file = File.join(destination, relative_path)
255
+
256
+ # Skip if destination file already exists (conflict)
257
+ if File.exist?(dest_file)
258
+ conflicts << relative_path
259
+ say_status :conflict, "app/#{new_dir}/#{relative_path} already exists, skipping", :red
260
+ next
261
+ end
262
+
263
+ FileUtils.mkdir_p(File.dirname(dest_file))
264
+ FileUtils.mv(file, dest_file)
265
+
266
+ wrap_in_namespace(dest_file, namespace)
267
+ say_status :migrated, "app/#{new_dir}/#{relative_path}", :green
268
+ migrated << relative_path
269
+ end
270
+
271
+ # Cleanup empty source directory
272
+ cleanup_empty_directory(source, old_dir)
273
+
274
+ # Track that migration happened for summary
275
+ instance_variable_set("@#{old_dir}_migrated", migrated.any?)
276
+
277
+ { migrated: migrated, conflicts: conflicts }
278
+ end
279
+
280
+ def wrap_in_namespace(file, namespace)
281
+ content = File.read(file)
282
+
283
+ # Skip if already namespaced
284
+ return if content.include?("module #{namespace}")
285
+
286
+ # Wrap content in namespace with proper indentation
287
+ indented = content.lines.map { |line| line.empty? || line.strip.empty? ? line : " #{line}" }.join
288
+ wrapped = "module #{namespace}\n#{indented}end\n"
289
+
290
+ File.write(file, wrapped)
291
+ end
292
+
293
+ def cleanup_empty_directory(dir, dir_name)
294
+ return unless File.directory?(dir)
295
+
296
+ # Remove empty subdirectories first (deepest first)
297
+ Dir.glob("#{dir}/**/", File::FNM_DOTMATCH).sort_by(&:length).reverse.each do |subdir|
298
+ next if subdir.end_with?(".", "..")
299
+
300
+ FileUtils.rmdir(subdir) if File.directory?(subdir) && Dir.empty?(subdir)
301
+ rescue Errno::ENOTEMPTY, Errno::ENOENT
302
+ # Directory not empty or already removed
303
+ end
304
+
305
+ # Remove main directory if empty
306
+ if File.directory?(dir) && Dir.empty?(dir)
307
+ FileUtils.rmdir(dir)
308
+ say_status :removed, "app/#{dir_name}/ (empty)", :yellow
309
+ end
310
+ rescue Errno::ENOTEMPTY
311
+ say_status :kept, "app/#{dir_name}/ (not empty)", :yellow
312
+ end
162
313
  end
163
314
  end