language-operator 0.1.61 → 0.1.62

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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/persona.md +9 -0
  3. data/.claude/commands/task.md +46 -1
  4. data/.rubocop.yml +13 -0
  5. data/.rubocop_custom/use_ux_helper.rb +44 -0
  6. data/CHANGELOG.md +8 -0
  7. data/Gemfile.lock +12 -1
  8. data/Makefile +26 -7
  9. data/Makefile.common +50 -0
  10. data/bin/aictl +8 -1
  11. data/components/agent/Gemfile +1 -1
  12. data/components/agent/bin/langop-agent +7 -0
  13. data/docs/README.md +58 -0
  14. data/docs/{dsl/best-practices.md → best-practices.md} +4 -4
  15. data/docs/cli-reference.md +274 -0
  16. data/docs/{dsl/constraints.md → constraints.md} +5 -5
  17. data/docs/how-agents-work.md +156 -0
  18. data/docs/installation.md +218 -0
  19. data/docs/quickstart.md +299 -0
  20. data/docs/understanding-generated-code.md +265 -0
  21. data/docs/using-tools.md +457 -0
  22. data/docs/webhooks.md +509 -0
  23. data/examples/ux_helpers_demo.rb +296 -0
  24. data/lib/language_operator/agent/base.rb +11 -1
  25. data/lib/language_operator/agent/executor.rb +23 -6
  26. data/lib/language_operator/agent/safety/safe_executor.rb +41 -39
  27. data/lib/language_operator/agent/task_executor.rb +346 -63
  28. data/lib/language_operator/agent/web_server.rb +110 -14
  29. data/lib/language_operator/agent/webhook_authenticator.rb +39 -5
  30. data/lib/language_operator/agent.rb +88 -2
  31. data/lib/language_operator/cli/base_command.rb +17 -11
  32. data/lib/language_operator/cli/command_loader.rb +72 -0
  33. data/lib/language_operator/cli/commands/agent/base.rb +837 -0
  34. data/lib/language_operator/cli/commands/agent/code_operations.rb +102 -0
  35. data/lib/language_operator/cli/commands/agent/helpers/cluster_llm_client.rb +116 -0
  36. data/lib/language_operator/cli/commands/agent/helpers/code_parser.rb +115 -0
  37. data/lib/language_operator/cli/commands/agent/helpers/synthesis_watcher.rb +96 -0
  38. data/lib/language_operator/cli/commands/agent/learning.rb +289 -0
  39. data/lib/language_operator/cli/commands/agent/lifecycle.rb +102 -0
  40. data/lib/language_operator/cli/commands/agent/logs.rb +125 -0
  41. data/lib/language_operator/cli/commands/agent/workspace.rb +327 -0
  42. data/lib/language_operator/cli/commands/cluster.rb +129 -84
  43. data/lib/language_operator/cli/commands/install.rb +1 -1
  44. data/lib/language_operator/cli/commands/model/base.rb +215 -0
  45. data/lib/language_operator/cli/commands/model/test.rb +165 -0
  46. data/lib/language_operator/cli/commands/persona.rb +16 -34
  47. data/lib/language_operator/cli/commands/quickstart.rb +3 -2
  48. data/lib/language_operator/cli/commands/status.rb +40 -67
  49. data/lib/language_operator/cli/commands/system/base.rb +44 -0
  50. data/lib/language_operator/cli/commands/system/exec.rb +147 -0
  51. data/lib/language_operator/cli/commands/system/helpers/llm_synthesis.rb +183 -0
  52. data/lib/language_operator/cli/commands/system/helpers/pod_manager.rb +212 -0
  53. data/lib/language_operator/cli/commands/system/helpers/template_loader.rb +57 -0
  54. data/lib/language_operator/cli/commands/system/helpers/template_validator.rb +174 -0
  55. data/lib/language_operator/cli/commands/system/schema.rb +92 -0
  56. data/lib/language_operator/cli/commands/system/synthesis_template.rb +151 -0
  57. data/lib/language_operator/cli/commands/system/synthesize.rb +224 -0
  58. data/lib/language_operator/cli/commands/system/validate_template.rb +130 -0
  59. data/lib/language_operator/cli/commands/tool/base.rb +271 -0
  60. data/lib/language_operator/cli/commands/tool/install.rb +255 -0
  61. data/lib/language_operator/cli/commands/tool/search.rb +69 -0
  62. data/lib/language_operator/cli/commands/tool/test.rb +115 -0
  63. data/lib/language_operator/cli/commands/use.rb +29 -6
  64. data/lib/language_operator/cli/errors/handler.rb +20 -17
  65. data/lib/language_operator/cli/errors/suggestions.rb +3 -5
  66. data/lib/language_operator/cli/errors/thor_errors.rb +55 -0
  67. data/lib/language_operator/cli/formatters/code_formatter.rb +4 -11
  68. data/lib/language_operator/cli/formatters/log_formatter.rb +8 -15
  69. data/lib/language_operator/cli/formatters/progress_formatter.rb +6 -8
  70. data/lib/language_operator/cli/formatters/status_formatter.rb +26 -7
  71. data/lib/language_operator/cli/formatters/table_formatter.rb +47 -36
  72. data/lib/language_operator/cli/formatters/value_formatter.rb +75 -0
  73. data/lib/language_operator/cli/helpers/cluster_context.rb +5 -3
  74. data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +2 -1
  75. data/lib/language_operator/cli/helpers/label_utils.rb +97 -0
  76. data/lib/language_operator/{ux/concerns/provider_helpers.rb → cli/helpers/provider_helper.rb} +10 -29
  77. data/lib/language_operator/cli/helpers/schedule_builder.rb +21 -1
  78. data/lib/language_operator/cli/helpers/user_prompts.rb +19 -11
  79. data/lib/language_operator/cli/helpers/ux_helper.rb +538 -0
  80. data/lib/language_operator/{ux/concerns/input_validation.rb → cli/helpers/validation_helper.rb} +13 -66
  81. data/lib/language_operator/cli/main.rb +50 -40
  82. data/lib/language_operator/cli/templates/tools/generic.yaml +3 -0
  83. data/lib/language_operator/cli/wizards/agent_wizard.rb +12 -20
  84. data/lib/language_operator/cli/wizards/model_wizard.rb +271 -0
  85. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +8 -34
  86. data/lib/language_operator/client/base.rb +28 -0
  87. data/lib/language_operator/client/config.rb +4 -1
  88. data/lib/language_operator/client/mcp_connector.rb +1 -1
  89. data/lib/language_operator/config/cluster_config.rb +3 -2
  90. data/lib/language_operator/config.rb +38 -11
  91. data/lib/language_operator/constants/kubernetes_labels.rb +80 -0
  92. data/lib/language_operator/constants.rb +13 -0
  93. data/lib/language_operator/dsl/http.rb +127 -10
  94. data/lib/language_operator/dsl.rb +153 -6
  95. data/lib/language_operator/errors.rb +50 -0
  96. data/lib/language_operator/kubernetes/client.rb +11 -6
  97. data/lib/language_operator/kubernetes/resource_builder.rb +58 -84
  98. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
  99. data/lib/language_operator/templates/schema/agent_dsl_schema.json +1 -1
  100. data/lib/language_operator/type_coercion.rb +118 -34
  101. data/lib/language_operator/utils/secure_path.rb +74 -0
  102. data/lib/language_operator/utils.rb +7 -0
  103. data/lib/language_operator/validators.rb +54 -2
  104. data/lib/language_operator/version.rb +1 -1
  105. data/synth/001/Makefile +10 -2
  106. data/synth/001/agent.rb +16 -15
  107. data/synth/001/output.log +27 -10
  108. data/synth/002/Makefile +10 -2
  109. data/synth/003/Makefile +1 -1
  110. data/synth/003/README.md +205 -133
  111. data/synth/003/agent.optimized.rb +66 -0
  112. data/synth/003/agent.synthesized.rb +41 -0
  113. metadata +111 -35
  114. data/docs/dsl/agent-reference.md +0 -604
  115. data/docs/dsl/mcp-integration.md +0 -1177
  116. data/docs/dsl/webhooks.md +0 -932
  117. data/docs/dsl/workflows.md +0 -744
  118. data/lib/language_operator/cli/commands/agent.rb +0 -1712
  119. data/lib/language_operator/cli/commands/model.rb +0 -366
  120. data/lib/language_operator/cli/commands/system.rb +0 -1259
  121. data/lib/language_operator/cli/commands/tool.rb +0 -654
  122. data/lib/language_operator/cli/formatters/optimization_formatter.rb +0 -226
  123. data/lib/language_operator/cli/helpers/pastel_helper.rb +0 -24
  124. data/lib/language_operator/learning/adapters/base_adapter.rb +0 -149
  125. data/lib/language_operator/learning/adapters/jaeger_adapter.rb +0 -221
  126. data/lib/language_operator/learning/adapters/signoz_adapter.rb +0 -435
  127. data/lib/language_operator/learning/adapters/tempo_adapter.rb +0 -239
  128. data/lib/language_operator/learning/optimizer.rb +0 -319
  129. data/lib/language_operator/learning/pattern_detector.rb +0 -260
  130. data/lib/language_operator/learning/task_synthesizer.rb +0 -288
  131. data/lib/language_operator/learning/trace_analyzer.rb +0 -285
  132. data/lib/language_operator/templates/task_synthesis.tmpl +0 -98
  133. data/lib/language_operator/ux/base.rb +0 -81
  134. data/lib/language_operator/ux/concerns/README.md +0 -155
  135. data/lib/language_operator/ux/concerns/headings.rb +0 -90
  136. data/lib/language_operator/ux/create_agent.rb +0 -255
  137. data/lib/language_operator/ux/create_model.rb +0 -267
  138. data/lib/language_operator/ux/quickstart.rb +0 -594
  139. data/synth/003/agent.rb +0 -41
  140. data/synth/003/output.log +0 -68
  141. /data/docs/{architecture/agent-runtime.md → agent-internals.md} +0 -0
  142. /data/docs/{dsl/chat-endpoints.md → chat-endpoints.md} +0 -0
  143. /data/docs/{dsl/SCHEMA_VERSION.md → schema-versioning.md} +0 -0
@@ -3,6 +3,7 @@
3
3
  require 'rack'
4
4
  require 'rackup'
5
5
  require 'mcp'
6
+ require_relative 'executor'
6
7
 
7
8
  module LanguageOperator
8
9
  module Agent
@@ -26,10 +27,13 @@ module LanguageOperator
26
27
  @agent = agent
27
28
  @port = port || ENV.fetch('PORT', '8080').to_i
28
29
  @routes = {}
29
- @executor = Executor.new(agent)
30
30
  @mcp_server = nil
31
31
  @mcp_transport = nil
32
32
 
33
+ # Initialize executor pool to prevent MCP connection leaks
34
+ @executor_pool_size = ENV.fetch('EXECUTOR_POOL_SIZE', '4').to_i
35
+ @executor_pool = setup_executor_pool
36
+
33
37
  setup_default_routes
34
38
  end
35
39
 
@@ -164,8 +168,49 @@ module LanguageOperator
164
168
  error_response(e)
165
169
  end
166
170
 
171
+ # Cleanup executor pool and connections
172
+ #
173
+ # Properly closes all executors in the pool and their MCP connections
174
+ # to prevent resource leaks during server shutdown.
175
+ #
176
+ # @return [void]
177
+ def cleanup
178
+ return unless @executor_pool
179
+
180
+ # Drain and cleanup all executors in the pool
181
+ executors_cleaned = 0
182
+ begin
183
+ loop do
184
+ executor = @executor_pool.pop(timeout: 0.1)
185
+ if executor
186
+ executor.cleanup_connections
187
+ executors_cleaned += 1
188
+ end
189
+ end
190
+ rescue ThreadError
191
+ # Pool is empty, we're done
192
+ end
193
+
194
+ puts "Cleaned up #{executors_cleaned} executors from pool"
195
+ end
196
+
167
197
  private
168
198
 
199
+ # Setup executor pool for connection reuse
200
+ #
201
+ # Creates a thread-safe queue pre-populated with executor instances
202
+ # to prevent creating new MCP connections for each webhook request.
203
+ #
204
+ # @return [Queue] Thread-safe executor pool
205
+ def setup_executor_pool
206
+ pool = Queue.new
207
+ @executor_pool_size.times do
208
+ pool << Executor.new(@agent)
209
+ end
210
+ puts "Initialized executor pool with #{@executor_pool_size} executors"
211
+ pool
212
+ end
213
+
169
214
  # Build the Rack application
170
215
  #
171
216
  # @return [Rack::Builder]
@@ -235,9 +280,11 @@ module LanguageOperator
235
280
  # @param request [Rack::Request] The request
236
281
  # @return [Hash] Request context
237
282
  def build_request_context(request)
238
- # Read body, handling nil case
283
+ # Read body, handling nil case and rewinding for subsequent reads
239
284
  body_content = if request.body
240
- request.body.read
285
+ content = request.body.read
286
+ request.body.rewind # Reset for subsequent reads by middleware/handlers
287
+ content
241
288
  else
242
289
  ''
243
290
  end
@@ -302,20 +349,48 @@ module LanguageOperator
302
349
 
303
350
  # Handle webhook request by executing agent
304
351
  #
352
+ # Uses executor pooling to prevent MCP connection resource leaks.
353
+ # Executors are reused across requests to avoid creating new
354
+ # connections for each webhook request.
355
+ #
305
356
  # @param context [Hash] Request context
306
357
  # @return [Hash] Response data
307
358
  def handle_webhook(context)
308
- # Execute agent with webhook context
309
- result = @executor.execute_with_context(
310
- instruction: 'Process incoming webhook request',
311
- context: context
312
- )
359
+ executor = nil
360
+ begin
361
+ # Get executor from pool with timeout
362
+ executor = @executor_pool.pop(timeout: 5)
363
+ result = executor.execute_with_context(
364
+ instruction: 'Process incoming webhook request',
365
+ context: context
366
+ )
313
367
 
314
- {
315
- status: 'processed',
316
- result: result,
317
- timestamp: Time.now.iso8601
318
- }
368
+ {
369
+ status: 'processed',
370
+ result: result,
371
+ timestamp: Time.now.iso8601
372
+ }
373
+ rescue ThreadError
374
+ # Pool exhausted, create temporary executor as fallback
375
+ temp_executor = Executor.new(@agent)
376
+ begin
377
+ result = temp_executor.execute_with_context(
378
+ instruction: 'Process incoming webhook request',
379
+ context: context
380
+ )
381
+
382
+ {
383
+ status: 'processed',
384
+ result: result,
385
+ timestamp: Time.now.iso8601
386
+ }
387
+ ensure
388
+ temp_executor.cleanup_connections
389
+ end
390
+ ensure
391
+ # Return executor to pool if we got one
392
+ @executor_pool.push(executor) if executor
393
+ end
319
394
  end
320
395
 
321
396
  # Handle MCP protocol request
@@ -536,15 +611,36 @@ module LanguageOperator
536
611
  class MockStream
537
612
  def initialize(buffer)
538
613
  @buffer = buffer
614
+ @closed = false
539
615
  end
540
616
 
541
617
  def write(data)
618
+ raise IOError, 'closed stream' if @closed
619
+
542
620
  @buffer.write(data)
543
621
  end
544
622
 
623
+ def flush
624
+ @buffer.flush if @buffer.respond_to?(:flush)
625
+ end
626
+
545
627
  def close
546
- # No-op
628
+ @closed = true
629
+ end
630
+
631
+ def closed?
632
+ @closed
633
+ end
634
+
635
+ def sync=(value)
636
+ # No-op for compatibility
637
+ end
638
+
639
+ # rubocop:disable Naming/PredicateMethod
640
+ def sync
641
+ true
547
642
  end
643
+ # rubocop:enable Naming/PredicateMethod
548
644
  end
549
645
 
550
646
  # Called by Rack to stream the response
@@ -2,11 +2,18 @@
2
2
 
3
3
  require 'openssl'
4
4
  require 'base64'
5
+ require_relative '../logger'
5
6
 
6
7
  module LanguageOperator
7
8
  module Agent
8
9
  # Executes webhook authentication checks
9
10
  class WebhookAuthenticator
11
+ @logger = LanguageOperator::Logger.new(component: 'WebhookAuth')
12
+
13
+ class << self
14
+ attr_reader :logger
15
+ end
16
+
10
17
  # Authenticate a webhook request
11
18
  #
12
19
  # @param authentication [LanguageOperator::Dsl::WebhookAuthentication] Authentication definition
@@ -88,19 +95,46 @@ module LanguageOperator
88
95
  expected_password = config[:password]
89
96
 
90
97
  auth_header = get_header(context, 'Authorization')
91
- return false unless auth_header
98
+ unless auth_header
99
+ logger.debug('Basic auth failed: missing Authorization header')
100
+ return false
101
+ end
92
102
 
93
103
  # Extract credentials from "Basic <base64>"
94
104
  match = auth_header.match(/^Basic\s+(.+)$/i)
95
- return false unless match
105
+ unless match
106
+ logger.debug('Basic auth failed: invalid Authorization header format')
107
+ return false
108
+ end
96
109
 
97
110
  begin
98
111
  credentials = Base64.decode64(match[1])
99
112
  username, password = credentials.split(':', 2)
100
113
 
101
- secure_compare(username, expected_username) &&
102
- secure_compare(password, expected_password)
103
- rescue StandardError
114
+ # Log debugging info for malformed credentials (no colon)
115
+ if password.nil?
116
+ logger.debug('Basic auth failed: malformed credentials (no colon separator)',
117
+ credentials_length: credentials.length,
118
+ contains_colon: credentials.include?(':'))
119
+ return false
120
+ end
121
+
122
+ username_valid = secure_compare(username, expected_username)
123
+ password_valid = secure_compare(password, expected_password)
124
+
125
+ success = username_valid && password_valid
126
+
127
+ unless success
128
+ logger.debug('Basic auth failed: credential mismatch',
129
+ username_valid: username_valid,
130
+ password_valid: password_valid)
131
+ end
132
+
133
+ success
134
+ rescue StandardError => e
135
+ logger.debug('Basic auth failed: exception during processing',
136
+ error: e.class.name,
137
+ message: e.message)
104
138
  false
105
139
  end
106
140
  end
@@ -157,8 +157,8 @@ module LanguageOperator
157
157
  case agent.mode
158
158
  when 'autonomous', 'interactive'
159
159
  if uses_dsl_v1
160
- # DSL v1: Execute main block with task executor
161
- execute_main_block(agent, agent_def)
160
+ # DSL v1: Execute main block with task executor in persistent mode
161
+ execute_main_block_persistent(agent, agent_def)
162
162
  elsif uses_dsl_v0
163
163
  # DSL v0: Execute workflow in autonomous mode
164
164
  executor = LanguageOperator::Agent::Executor.new(agent)
@@ -233,6 +233,92 @@ module LanguageOperator
233
233
  result
234
234
  end
235
235
 
236
+ # Execute main block (DSL v1) in persistent mode for autonomous agents
237
+ #
238
+ # @param agent [LanguageOperator::Agent::Base] The agent instance
239
+ # @param agent_def [LanguageOperator::Dsl::AgentDefinition] The agent definition
240
+ # @return [void]
241
+ def self.execute_main_block_persistent(agent, agent_def)
242
+ shutdown_requested = setup_signal_handlers
243
+
244
+ logger.info('Starting agent in persistent autonomous mode',
245
+ agent_name: agent_def.name,
246
+ task_count: agent_def.tasks.size)
247
+
248
+ # Execute initial main block
249
+ execute_main_block(agent, agent_def)
250
+ logger.info('Initial task completed - entering idle state')
251
+
252
+ # Enter waiting loop for additional instructions
253
+ run_idle_loop(shutdown_requested)
254
+
255
+ logger.info('Graceful shutdown requested - agent exiting')
256
+ end
257
+
258
+ # Setup signal handlers for graceful shutdown
259
+ #
260
+ # @return [Proc] shutdown_requested flag wrapped in a closure
261
+ def self.setup_signal_handlers
262
+ shutdown_requested = false
263
+ trap('TERM') do
264
+ logger.info('SIGTERM received - requesting graceful shutdown')
265
+ shutdown_requested = true
266
+ end
267
+ trap('INT') do
268
+ logger.info('SIGINT received - requesting graceful shutdown')
269
+ shutdown_requested = true
270
+ end
271
+
272
+ -> { shutdown_requested }
273
+ end
274
+
275
+ # Run the idle loop waiting for new instructions
276
+ #
277
+ # @param shutdown_requested [Proc] Closure that returns shutdown state
278
+ # @return [void]
279
+ def self.run_idle_loop(shutdown_requested)
280
+ idle_timeout = ENV.fetch('AGENT_IDLE_TIMEOUT', '300').to_i # 5 minutes default
281
+ last_activity = Time.now
282
+
283
+ until shutdown_requested.call
284
+ begin
285
+ last_activity = Time.now if handle_new_instruction
286
+
287
+ sleep 5 # Avoid busy waiting
288
+
289
+ # Reset idle timer when timeout reached (stay persistent)
290
+ if idle_timeout.positive? && Time.now - last_activity > idle_timeout
291
+ logger.info('Idle timeout reached - agent remaining available',
292
+ timeout_seconds: idle_timeout)
293
+ last_activity = Time.now
294
+ end
295
+ rescue StandardError => e
296
+ logger.error('Error in idle loop',
297
+ error: e.message,
298
+ backtrace: e.backtrace[0..3])
299
+ sleep 10 # Back off on error
300
+ end
301
+ end
302
+ end
303
+
304
+ # Handle new instruction from environment variable
305
+ #
306
+ # @return [Boolean] True if instruction was processed
307
+ def self.handle_new_instruction
308
+ new_instruction = ENV.fetch('AGENT_NEW_INSTRUCTION', nil)
309
+ return false unless new_instruction && !new_instruction.strip.empty?
310
+
311
+ logger.info('New instruction received',
312
+ instruction: new_instruction[0..100])
313
+
314
+ # Clear the environment variable to prevent re-execution
315
+ ENV['AGENT_NEW_INSTRUCTION'] = ''
316
+
317
+ # Execute the new instruction (implementation would depend on requirements)
318
+ logger.info('Instruction acknowledged - agent remains in autonomous mode')
319
+ true
320
+ end
321
+
236
322
  # Execute the output handler (neural or symbolic)
237
323
  #
238
324
  # @param agent_def [LanguageOperator::Dsl::AgentDefinition] The agent definition
@@ -1,11 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'thor'
4
+ require_relative 'helpers/ux_helper'
4
5
 
5
6
  module LanguageOperator
6
7
  module CLI
7
8
  # Base class for all CLI commands providing shared functionality
9
+ #
10
+ # Automatically includes {Helpers::UxHelper} to provide:
11
+ # - +pastel+ - Colorized terminal output
12
+ # - +prompt+ - Interactive user prompts
13
+ #
14
+ # All commands inheriting from BaseCommand get these helpers automatically.
8
15
  class BaseCommand < Thor
16
+ include Helpers::UxHelper
17
+
9
18
  no_commands do
10
19
  # Lazy-initialized cluster context from options
11
20
  # @return [Helpers::ClusterContext]
@@ -55,21 +64,16 @@ module LanguageOperator
55
64
  exit 1
56
65
  end
57
66
 
58
- # Confirm resource deletion with user
67
+ # Confirm resource deletion with user (delegates to UxHelper)
59
68
  # @param resource_type [String] Type of resource being deleted
60
69
  # @param name [String] Resource name
61
70
  # @param cluster [String] Cluster name
62
- # @param details [Hash] Additional details to display
63
71
  # @param force [Boolean] Skip confirmation if true
64
72
  # @return [Boolean] True if deletion should proceed
65
- def confirm_deletion(resource_type, name, cluster, details: {}, force: false)
73
+ def confirm_deletion_with_force(resource_type, name, cluster, force: false)
66
74
  return true if force
67
75
 
68
- puts "This will delete #{resource_type} '#{name}' from cluster '#{cluster}':"
69
- details.each { |key, value| puts " #{key}: #{value}" }
70
- puts
71
-
72
- Helpers::UserPrompts.confirm('Are you sure?')
76
+ confirm_deletion(resource_type, name, cluster)
73
77
  end
74
78
 
75
79
  # Check if resource has dependencies and confirm deletion
@@ -94,7 +98,7 @@ module LanguageOperator
94
98
  puts 'Delete these agents first, or use --force to delete anyway.'
95
99
  puts
96
100
 
97
- return Helpers::UserPrompts.confirm('Are you sure?')
101
+ return CLI::Helpers::UserPrompts.confirm('Are you sure?')
98
102
  end
99
103
 
100
104
  true
@@ -102,14 +106,16 @@ module LanguageOperator
102
106
 
103
107
  # List resources or handle empty state
104
108
  # @param type [String] Resource type (e.g., 'LanguageModel')
109
+ # @param resource_name [String] Human-readable resource name (e.g., 'models')
105
110
  # @param empty_message [String, nil] Custom message when no resources found
106
111
  # @yield Optional block to execute when list is empty (for guidance)
107
112
  # @return [Array<Hash>] List of resources (empty array if none found)
108
- def list_resources_or_empty(type, empty_message: nil)
113
+ def list_resources_or_empty(type, resource_name: nil, empty_message: nil)
109
114
  resources = ctx.client.list_resources(type, namespace: ctx.namespace)
110
115
 
111
116
  if resources.empty?
112
- msg = empty_message || "No #{type} resources found in cluster '#{ctx.name}'"
117
+ resource_display = resource_name || type.downcase.gsub('language', '')
118
+ msg = empty_message || "No #{resource_display} found"
113
119
  Formatters::ProgressFormatter.info(msg)
114
120
  yield if block_given?
115
121
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ # CommandLoader - Single require point for all CLI commands
4
+ #
5
+ # This module eliminates duplication across command files by auto-loading
6
+ # all common formatters, helpers, and dependencies.
7
+ #
8
+ # Before CommandLoader, each command file had 15+ duplicate require statements.
9
+ # Now commands just need: require_relative '../command_loader'
10
+ #
11
+ # Usage in command files:
12
+ # require_relative '../command_loader'
13
+ # require_relative '../wizards/my_wizard' # command-specific requires only
14
+ #
15
+ # class MyCommand < BaseCommand
16
+ # include Constants # For RESOURCE_* constants
17
+ #
18
+ # def some_method
19
+ # ctx.client.get_resource(RESOURCE_AGENT, name, ctx.namespace)
20
+ # end
21
+ # end
22
+
23
+ module LanguageOperator
24
+ module CLI
25
+ module CommandLoader
26
+ # Auto-load all common CLI dependencies
27
+ # This runs once when the module is first required
28
+ def self.setup
29
+ # Core base class (must load first)
30
+ require_relative 'base_command'
31
+
32
+ # Constants for resource types
33
+ require_relative '../constants'
34
+
35
+ # Formatters - visual output and progress indicators
36
+ require_relative 'formatters/progress_formatter'
37
+ require_relative 'formatters/table_formatter'
38
+ require_relative 'formatters/value_formatter'
39
+ require_relative 'formatters/log_formatter'
40
+ require_relative 'formatters/status_formatter'
41
+ require_relative 'formatters/code_formatter'
42
+ require_relative 'formatters/log_style'
43
+ # require_relative 'formatters/optimization_formatter'
44
+
45
+ # Helpers - shared utilities and validations
46
+ require_relative 'helpers/cluster_validator'
47
+ require_relative 'helpers/cluster_context'
48
+ require_relative 'helpers/user_prompts'
49
+ require_relative 'helpers/editor_helper'
50
+ require_relative 'helpers/resource_dependency_checker'
51
+ require_relative 'helpers/ux_helper'
52
+ require_relative 'helpers/validation_helper'
53
+ require_relative 'helpers/provider_helper'
54
+ require_relative 'helpers/schedule_builder'
55
+ require_relative 'helpers/kubeconfig_validator'
56
+
57
+ # Error handling
58
+ require_relative 'errors/handler'
59
+ require_relative 'errors/suggestions'
60
+
61
+ # Configuration and Kubernetes clients
62
+ require_relative '../config/cluster_config'
63
+ require_relative '../config/tool_registry'
64
+ require_relative '../kubernetes/client'
65
+ require_relative '../kubernetes/resource_builder'
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ # Auto-setup when this file is required
72
+ LanguageOperator::CLI::CommandLoader.setup