language-operator 0.1.30 → 0.1.31

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/Gemfile.lock +1 -1
  4. data/Makefile +7 -2
  5. data/Rakefile +29 -0
  6. data/docs/dsl/SCHEMA_VERSION.md +250 -0
  7. data/docs/dsl/agent-reference.md +13 -0
  8. data/lib/language_operator/agent/safety/safe_executor.rb +12 -0
  9. data/lib/language_operator/cli/commands/agent.rb +54 -101
  10. data/lib/language_operator/cli/commands/cluster.rb +37 -1
  11. data/lib/language_operator/cli/commands/persona.rb +2 -5
  12. data/lib/language_operator/cli/commands/status.rb +5 -18
  13. data/lib/language_operator/cli/commands/system.rb +772 -0
  14. data/lib/language_operator/cli/formatters/code_formatter.rb +3 -7
  15. data/lib/language_operator/cli/formatters/log_formatter.rb +3 -5
  16. data/lib/language_operator/cli/formatters/progress_formatter.rb +3 -7
  17. data/lib/language_operator/cli/formatters/status_formatter.rb +37 -0
  18. data/lib/language_operator/cli/formatters/table_formatter.rb +10 -26
  19. data/lib/language_operator/cli/helpers/pastel_helper.rb +24 -0
  20. data/lib/language_operator/cli/main.rb +4 -0
  21. data/lib/language_operator/dsl/schema.rb +1102 -0
  22. data/lib/language_operator/dsl.rb +1 -0
  23. data/lib/language_operator/logger.rb +4 -4
  24. data/lib/language_operator/templates/README.md +23 -0
  25. data/lib/language_operator/templates/examples/agent_synthesis.tmpl +115 -0
  26. data/lib/language_operator/templates/examples/persona_distillation.tmpl +19 -0
  27. data/lib/language_operator/templates/schema/.gitkeep +0 -0
  28. data/lib/language_operator/templates/schema/CHANGELOG.md +93 -0
  29. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +306 -0
  30. data/lib/language_operator/templates/schema/agent_dsl_schema.json +452 -0
  31. data/lib/language_operator/version.rb +1 -1
  32. data/requirements/tasks/iterate.md +2 -2
  33. metadata +13 -9
  34. data/examples/README.md +0 -569
  35. data/examples/agent_example.rb +0 -86
  36. data/examples/chat_endpoint_agent.rb +0 -118
  37. data/examples/github_webhook_agent.rb +0 -171
  38. data/examples/mcp_agent.rb +0 -158
  39. data/examples/oauth_callback_agent.rb +0 -296
  40. data/examples/stripe_webhook_agent.rb +0 -219
  41. data/examples/webhook_agent.rb +0 -80
@@ -4,10 +4,13 @@ require 'thor'
4
4
  require_relative '../formatters/progress_formatter'
5
5
  require_relative '../formatters/table_formatter'
6
6
  require_relative '../formatters/value_formatter'
7
+ require_relative '../formatters/log_formatter'
8
+ require_relative '../formatters/status_formatter'
7
9
  require_relative '../helpers/cluster_validator'
8
10
  require_relative '../helpers/cluster_context'
9
11
  require_relative '../helpers/user_prompts'
10
12
  require_relative '../helpers/editor_helper'
13
+ require_relative '../helpers/pastel_helper'
11
14
  require_relative '../errors/handler'
12
15
  require_relative '../../config/cluster_config'
13
16
  require_relative '../../kubernetes/client'
@@ -19,6 +22,7 @@ module LanguageOperator
19
22
  # Agent management commands
20
23
  class Agent < Thor
21
24
  include Helpers::ClusterValidator
25
+ include Helpers::PastelHelper
22
26
 
23
27
  desc 'create [DESCRIPTION]', 'Create a new agent with natural language description'
24
28
  long_desc <<-DESC
@@ -69,9 +73,9 @@ module LanguageOperator
69
73
  cluster = Helpers::ClusterValidator.get_cluster(options[:cluster])
70
74
  end
71
75
 
72
- cluster_config = Helpers::ClusterValidator.get_cluster_config(cluster)
76
+ ctx = Helpers::ClusterContext.from_options(options.merge(cluster: cluster))
73
77
 
74
- Formatters::ProgressFormatter.info("Creating agent in cluster '#{cluster}'")
78
+ Formatters::ProgressFormatter.info("Creating agent in cluster '#{ctx.name}'")
75
79
  puts
76
80
 
77
81
  # Generate agent name from description if not provided
@@ -80,18 +84,17 @@ module LanguageOperator
80
84
  # Get models: use specified models, or default to all available models in cluster
81
85
  models = options[:models]
82
86
  if models.nil? || models.empty?
83
- k8s = Helpers::ClusterValidator.kubernetes_client(options[:cluster])
84
- available_models = k8s.list_resources('LanguageModel', namespace: cluster_config[:namespace])
87
+ available_models = ctx.client.list_resources('LanguageModel', namespace: ctx.namespace)
85
88
  models = available_models.map { |m| m.dig('metadata', 'name') }
86
89
 
87
- Errors::Handler.handle_no_models_available(cluster: cluster) if models.empty?
90
+ Errors::Handler.handle_no_models_available(cluster: ctx.name) if models.empty?
88
91
  end
89
92
 
90
93
  # Build LanguageAgent resource
91
94
  agent_resource = Kubernetes::ResourceBuilder.language_agent(
92
95
  agent_name,
93
96
  instructions: description,
94
- cluster: cluster_config[:namespace],
97
+ cluster: ctx.namespace,
95
98
  persona: options[:persona],
96
99
  tools: options[:tools] || [],
97
100
  models: models
@@ -99,29 +102,26 @@ module LanguageOperator
99
102
 
100
103
  # Dry-run mode: preview without applying
101
104
  if options[:dry_run]
102
- display_dry_run_preview(agent_resource, cluster, description)
105
+ display_dry_run_preview(agent_resource, ctx.name, description)
103
106
  return
104
107
  end
105
108
 
106
- # Connect to Kubernetes
107
- k8s = Helpers::ClusterValidator.kubernetes_client(options[:cluster])
108
-
109
109
  # Apply resource to cluster
110
110
  Formatters::ProgressFormatter.with_spinner("Creating agent '#{agent_name}'") do
111
- k8s.apply_resource(agent_resource)
111
+ ctx.client.apply_resource(agent_resource)
112
112
  end
113
113
 
114
114
  # Watch synthesis status
115
- synthesis_result = watch_synthesis_status(k8s, agent_name, cluster_config[:namespace])
115
+ synthesis_result = watch_synthesis_status(ctx.client, agent_name, ctx.namespace)
116
116
 
117
117
  # Exit if synthesis failed
118
118
  exit 1 unless synthesis_result[:success]
119
119
 
120
120
  # Fetch the updated agent to get complete details
121
- agent = k8s.get_resource('LanguageAgent', agent_name, cluster_config[:namespace])
121
+ agent = ctx.client.get_resource('LanguageAgent', agent_name, ctx.namespace)
122
122
 
123
123
  # Display enhanced success output
124
- display_agent_created(agent, cluster, description, synthesis_result)
124
+ display_agent_created(agent, ctx.name, description, synthesis_result)
125
125
  rescue StandardError => e
126
126
  Formatters::ProgressFormatter.error("Failed to create agent: #{e.message}")
127
127
  raise if ENV['DEBUG']
@@ -136,8 +136,8 @@ module LanguageOperator
136
136
  if options[:all_clusters]
137
137
  list_all_clusters
138
138
  else
139
- cluster = Helpers::ClusterValidator.get_cluster(options[:cluster])
140
- list_cluster_agents(cluster)
139
+ ctx = Helpers::ClusterContext.from_options(options)
140
+ list_cluster_agents(ctx.name)
141
141
  end
142
142
  rescue StandardError => e
143
143
  Formatters::ProgressFormatter.error("Failed to list agents: #{e.message}")
@@ -149,16 +149,13 @@ module LanguageOperator
149
149
  desc 'inspect NAME', 'Show detailed agent information'
150
150
  option :cluster, type: :string, desc: 'Override current cluster context'
151
151
  def inspect(name)
152
- cluster = Helpers::ClusterValidator.get_cluster(options[:cluster])
153
- cluster_config = Helpers::ClusterValidator.get_cluster_config(cluster)
154
-
155
- k8s = Helpers::ClusterValidator.kubernetes_client(options[:cluster])
152
+ ctx = Helpers::ClusterContext.from_options(options)
156
153
 
157
- agent = k8s.get_resource('LanguageAgent', name, cluster_config[:namespace])
154
+ agent = ctx.client.get_resource('LanguageAgent', name, ctx.namespace)
158
155
 
159
156
  puts "Agent: #{name}"
160
- puts " Cluster: #{cluster}"
161
- puts " Namespace: #{cluster_config[:namespace]}"
157
+ puts " Cluster: #{ctx.name}"
158
+ puts " Namespace: #{ctx.namespace}"
162
159
  puts
163
160
 
164
161
  # Status
@@ -234,7 +231,7 @@ module LanguageOperator
234
231
  # Recent events (if available)
235
232
  # This would require querying events, which we can add later
236
233
  rescue K8s::Error::NotFound
237
- handle_agent_not_found(name, cluster, k8s, cluster_config)
234
+ handle_agent_not_found(name, ctx)
238
235
  rescue StandardError => e
239
236
  Formatters::ProgressFormatter.error("Failed to inspect agent: #{e.message}")
240
237
  raise if ENV['DEBUG']
@@ -292,25 +289,22 @@ module LanguageOperator
292
289
  option :follow, type: :boolean, aliases: '-f', default: false, desc: 'Follow logs'
293
290
  option :tail, type: :numeric, default: 100, desc: 'Number of lines to show from the end'
294
291
  def logs(name)
295
- cluster = Helpers::ClusterValidator.get_cluster(options[:cluster])
296
- cluster_config = Helpers::ClusterValidator.get_cluster_config(cluster)
297
-
298
- k8s = Helpers::ClusterValidator.kubernetes_client(options[:cluster])
292
+ ctx = Helpers::ClusterContext.from_options(options)
299
293
 
300
294
  # Get agent to determine the pod name
301
295
  begin
302
- agent = k8s.get_resource('LanguageAgent', name, cluster_config[:namespace])
296
+ agent = ctx.client.get_resource('LanguageAgent', name, ctx.namespace)
303
297
  rescue K8s::Error::NotFound
304
- Formatters::ProgressFormatter.error("Agent '#{name}' not found in cluster '#{cluster}'")
298
+ Formatters::ProgressFormatter.error("Agent '#{name}' not found in cluster '#{ctx.name}'")
305
299
  exit 1
306
300
  end
307
301
 
308
302
  mode = agent.dig('spec', 'mode') || 'autonomous'
309
303
 
310
304
  # Build kubectl command for log streaming
311
- kubeconfig_arg = cluster_config[:kubeconfig] ? "--kubeconfig=#{cluster_config[:kubeconfig]}" : ''
312
- context_arg = cluster_config[:context] ? "--context=#{cluster_config[:context]}" : ''
313
- namespace_arg = "-n #{cluster_config[:namespace]}"
305
+ kubeconfig_arg = ctx.config[:kubeconfig] ? "--kubeconfig=#{ctx.config[:kubeconfig]}" : ''
306
+ context_arg = ctx.config[:context] ? "--context=#{ctx.config[:context]}" : ''
307
+ namespace_arg = "-n #{ctx.namespace}"
314
308
  tail_arg = "--tail=#{options[:tail]}"
315
309
  follow_arg = options[:follow] ? '-f' : ''
316
310
 
@@ -367,15 +361,12 @@ module LanguageOperator
367
361
  def code(name)
368
362
  require_relative '../formatters/code_formatter'
369
363
 
370
- cluster = Helpers::ClusterValidator.get_cluster(options[:cluster])
371
- cluster_config = Helpers::ClusterValidator.get_cluster_config(cluster)
372
-
373
- k8s = Helpers::ClusterValidator.kubernetes_client(options[:cluster])
364
+ ctx = Helpers::ClusterContext.from_options(options)
374
365
 
375
366
  # Get the code ConfigMap for this agent
376
367
  configmap_name = "#{name}-code"
377
368
  begin
378
- configmap = k8s.get_resource('ConfigMap', configmap_name, cluster_config[:namespace])
369
+ configmap = ctx.client.get_resource('ConfigMap', configmap_name, ctx.namespace)
379
370
  rescue K8s::Error::NotFound
380
371
  Formatters::ProgressFormatter.error("Synthesized code not found for agent '#{name}'")
381
372
  puts
@@ -414,16 +405,13 @@ module LanguageOperator
414
405
  desc 'edit NAME', 'Edit agent instructions'
415
406
  option :cluster, type: :string, desc: 'Override current cluster context'
416
407
  def edit(name)
417
- cluster = Helpers::ClusterValidator.get_cluster(options[:cluster])
418
- cluster_config = Helpers::ClusterValidator.get_cluster_config(cluster)
419
-
420
- k8s = Helpers::ClusterValidator.kubernetes_client(options[:cluster])
408
+ ctx = Helpers::ClusterContext.from_options(options)
421
409
 
422
410
  # Get current agent
423
411
  begin
424
- agent = k8s.get_resource('LanguageAgent', name, cluster_config[:namespace])
412
+ agent = ctx.client.get_resource('LanguageAgent', name, ctx.namespace)
425
413
  rescue K8s::Error::NotFound
426
- Formatters::ProgressFormatter.error("Agent '#{name}' not found in cluster '#{cluster}'")
414
+ Formatters::ProgressFormatter.error("Agent '#{name}' not found in cluster '#{ctx.name}'")
427
415
  exit 1
428
416
  end
429
417
 
@@ -465,16 +453,13 @@ module LanguageOperator
465
453
  desc 'pause NAME', 'Pause scheduled agent execution'
466
454
  option :cluster, type: :string, desc: 'Override current cluster context'
467
455
  def pause(name)
468
- cluster = Helpers::ClusterValidator.get_cluster(options[:cluster])
469
- cluster_config = Helpers::ClusterValidator.get_cluster_config(cluster)
470
-
471
- k8s = Helpers::ClusterValidator.kubernetes_client(options[:cluster])
456
+ ctx = Helpers::ClusterContext.from_options(options)
472
457
 
473
458
  # Get agent
474
459
  begin
475
- agent = k8s.get_resource('LanguageAgent', name, cluster_config[:namespace])
460
+ agent = ctx.client.get_resource('LanguageAgent', name, ctx.namespace)
476
461
  rescue K8s::Error::NotFound
477
- Formatters::ProgressFormatter.error("Agent '#{name}' not found in cluster '#{cluster}'")
462
+ Formatters::ProgressFormatter.error("Agent '#{name}' not found in cluster '#{ctx.name}'")
478
463
  exit 1
479
464
  end
480
465
 
@@ -490,12 +475,12 @@ module LanguageOperator
490
475
  # Suspend the CronJob by setting spec.suspend = true
491
476
  # This is done by patching the underlying CronJob resource
492
477
  cronjob_name = name
493
- namespace = cluster_config[:namespace]
478
+ namespace = ctx.namespace
494
479
 
495
480
  Formatters::ProgressFormatter.with_spinner("Pausing agent '#{name}'") do
496
481
  # Use kubectl to patch the cronjob
497
- kubeconfig_arg = cluster_config[:kubeconfig] ? "--kubeconfig=#{cluster_config[:kubeconfig]}" : ''
498
- context_arg = cluster_config[:context] ? "--context=#{cluster_config[:context]}" : ''
482
+ kubeconfig_arg = ctx.config[:kubeconfig] ? "--kubeconfig=#{ctx.config[:kubeconfig]}" : ''
483
+ context_arg = ctx.config[:context] ? "--context=#{ctx.config[:context]}" : ''
499
484
 
500
485
  cmd = "kubectl #{kubeconfig_arg} #{context_arg} -n #{namespace} patch cronjob #{cronjob_name} -p '{\"spec\":{\"suspend\":true}}'"
501
486
  system(cmd)
@@ -517,16 +502,13 @@ module LanguageOperator
517
502
  desc 'resume NAME', 'Resume paused agent'
518
503
  option :cluster, type: :string, desc: 'Override current cluster context'
519
504
  def resume(name)
520
- cluster = Helpers::ClusterValidator.get_cluster(options[:cluster])
521
- cluster_config = Helpers::ClusterValidator.get_cluster_config(cluster)
522
-
523
- k8s = Helpers::ClusterValidator.kubernetes_client(options[:cluster])
505
+ ctx = Helpers::ClusterContext.from_options(options)
524
506
 
525
507
  # Get agent
526
508
  begin
527
- agent = k8s.get_resource('LanguageAgent', name, cluster_config[:namespace])
509
+ agent = ctx.client.get_resource('LanguageAgent', name, ctx.namespace)
528
510
  rescue K8s::Error::NotFound
529
- Formatters::ProgressFormatter.error("Agent '#{name}' not found in cluster '#{cluster}'")
511
+ Formatters::ProgressFormatter.error("Agent '#{name}' not found in cluster '#{ctx.name}'")
530
512
  exit 1
531
513
  end
532
514
 
@@ -540,12 +522,12 @@ module LanguageOperator
540
522
 
541
523
  # Resume the CronJob by setting spec.suspend = false
542
524
  cronjob_name = name
543
- namespace = cluster_config[:namespace]
525
+ namespace = ctx.namespace
544
526
 
545
527
  Formatters::ProgressFormatter.with_spinner("Resuming agent '#{name}'") do
546
528
  # Use kubectl to patch the cronjob
547
- kubeconfig_arg = cluster_config[:kubeconfig] ? "--kubeconfig=#{cluster_config[:kubeconfig]}" : ''
548
- context_arg = cluster_config[:context] ? "--context=#{cluster_config[:context]}" : ''
529
+ kubeconfig_arg = ctx.config[:kubeconfig] ? "--kubeconfig=#{ctx.config[:kubeconfig]}" : ''
530
+ context_arg = ctx.config[:context] ? "--context=#{ctx.config[:context]}" : ''
549
531
 
550
532
  cmd = "kubectl #{kubeconfig_arg} #{context_arg} -n #{namespace} patch cronjob #{cronjob_name} -p '{\"spec\":{\"suspend\":false}}'"
551
533
  system(cmd)
@@ -619,24 +601,21 @@ module LanguageOperator
619
601
 
620
602
  private
621
603
 
622
- def handle_agent_not_found(name, cluster, k8s, cluster_config)
604
+ def handle_agent_not_found(name, ctx)
623
605
  # Get available agents for fuzzy matching
624
- agents = k8s.list_resources('LanguageAgent', namespace: cluster_config[:namespace])
606
+ agents = ctx.client.list_resources('LanguageAgent', namespace: ctx.namespace)
625
607
  available_names = agents.map { |a| a.dig('metadata', 'name') }
626
608
 
627
609
  error = K8s::Error::NotFound.new(404, 'Not Found', 'LanguageAgent')
628
610
  Errors::Handler.handle_not_found(error,
629
611
  resource_type: 'LanguageAgent',
630
612
  resource_name: name,
631
- cluster: cluster,
613
+ cluster: ctx.name,
632
614
  available_resources: available_names)
633
615
  end
634
616
 
635
617
  def display_agent_created(agent, cluster, _description, synthesis_result)
636
- require 'pastel'
637
618
  require_relative '../formatters/code_formatter'
638
-
639
- pastel = Pastel.new
640
619
  agent_name = agent.dig('metadata', 'name')
641
620
 
642
621
  puts
@@ -645,10 +624,9 @@ module LanguageOperator
645
624
 
646
625
  # Get synthesized code if available
647
626
  begin
648
- cluster_config = Helpers::ClusterValidator.get_cluster_config(cluster)
649
- k8s = Helpers::ClusterValidator.kubernetes_client(cluster)
627
+ ctx = Helpers::ClusterContext.from_options(cluster: cluster)
650
628
  configmap_name = "#{agent_name}-code"
651
- configmap = k8s.get_resource('ConfigMap', configmap_name, cluster_config[:namespace])
629
+ configmap = ctx.client.get_resource('ConfigMap', configmap_name, ctx.namespace)
652
630
  code_content = configmap.dig('data', 'agent.rb')
653
631
 
654
632
  if code_content
@@ -832,21 +810,7 @@ module LanguageOperator
832
810
  end
833
811
 
834
812
  def format_status(status)
835
- require 'pastel'
836
- pastel = Pastel.new
837
-
838
- case status.downcase
839
- when 'ready', 'running', 'active'
840
- "#{pastel.green('●')} #{status}"
841
- when 'pending', 'creating', 'synthesizing'
842
- "#{pastel.yellow('●')} #{status}"
843
- when 'failed', 'error'
844
- "#{pastel.red('●')} #{status}"
845
- when 'paused', 'stopped'
846
- "#{pastel.dim('●')} #{status}"
847
- else
848
- "#{pastel.dim('●')} #{status}"
849
- end
813
+ Formatters::StatusFormatter.format(status)
850
814
  end
851
815
 
852
816
  def generate_agent_name(description)
@@ -940,13 +904,11 @@ module LanguageOperator
940
904
  end
941
905
 
942
906
  def list_cluster_agents(cluster)
943
- cluster_config = Helpers::ClusterValidator.get_cluster_config(cluster)
907
+ ctx = Helpers::ClusterContext.from_options(cluster: cluster)
944
908
 
945
909
  Formatters::ProgressFormatter.info("Agents in cluster '#{cluster}'")
946
910
 
947
- k8s = Helpers::ClusterValidator.kubernetes_client(cluster)
948
-
949
- agents = k8s.list_resources('LanguageAgent', namespace: cluster_config[:namespace])
911
+ agents = ctx.client.list_resources('LanguageAgent', namespace: ctx.namespace)
950
912
 
951
913
  table_data = agents.map do |agent|
952
914
  {
@@ -981,9 +943,9 @@ module LanguageOperator
981
943
  all_agents = []
982
944
 
983
945
  clusters.each do |cluster|
984
- k8s = Helpers::ClusterValidator.kubernetes_client(cluster[:name])
946
+ ctx = Helpers::ClusterContext.from_options(cluster: cluster[:name])
985
947
 
986
- agents = k8s.list_resources('LanguageAgent', namespace: cluster[:namespace])
948
+ agents = ctx.client.list_resources('LanguageAgent', namespace: ctx.namespace)
987
949
 
988
950
  agents.each do |agent|
989
951
  all_agents << {
@@ -1059,9 +1021,6 @@ module LanguageOperator
1059
1021
  end
1060
1022
 
1061
1023
  def list_workspace_files(ctx, agent_name)
1062
- require 'pastel'
1063
- pastel = Pastel.new
1064
-
1065
1024
  pod_name = get_agent_pod(ctx, agent_name)
1066
1025
 
1067
1026
  # Check if workspace directory exists
@@ -1148,9 +1107,6 @@ module LanguageOperator
1148
1107
  end
1149
1108
 
1150
1109
  def view_workspace_file(ctx, agent_name, file_path)
1151
- require 'pastel'
1152
- pastel = Pastel.new
1153
-
1154
1110
  pod_name = get_agent_pod(ctx, agent_name)
1155
1111
 
1156
1112
  # Check if file exists
@@ -1191,9 +1147,6 @@ module LanguageOperator
1191
1147
  end
1192
1148
 
1193
1149
  def clean_workspace(ctx, agent_name)
1194
- require 'pastel'
1195
- pastel = Pastel.new
1196
-
1197
1150
  pod_name = get_agent_pod(ctx, agent_name)
1198
1151
 
1199
1152
  # Get current workspace usage
@@ -121,6 +121,7 @@ module LanguageOperator
121
121
  end
122
122
 
123
123
  # Build table data
124
+ # rubocop:disable Metrics/BlockLength
124
125
  table_data = clusters.map do |cluster|
125
126
  k8s = Helpers::ClusterValidator.kubernetes_client(cluster[:name])
126
127
 
@@ -144,9 +145,26 @@ module LanguageOperator
144
145
  models: models.count,
145
146
  status: status
146
147
  }
148
+ rescue K8s::Error::NotFound
149
+ # Cluster exists in local config but not in Kubernetes
150
+ name_display = cluster[:name]
151
+ name_display += ' *' if cluster[:name] == current
152
+
153
+ {
154
+ name: name_display,
155
+ namespace: cluster[:namespace],
156
+ agents: '-',
157
+ tools: '-',
158
+ models: '-',
159
+ status: 'Not Found'
160
+ }
147
161
  rescue StandardError
162
+ # Other errors (connection issues, auth problems, etc.)
163
+ name_display = cluster[:name]
164
+ name_display += ' *' if cluster[:name] == current
165
+
148
166
  {
149
- name: cluster[:name],
167
+ name: name_display,
150
168
  namespace: cluster[:namespace],
151
169
  agents: '?',
152
170
  tools: '?',
@@ -154,6 +172,7 @@ module LanguageOperator
154
172
  status: 'Error'
155
173
  }
156
174
  end
175
+ # rubocop:enable Metrics/BlockLength
157
176
 
158
177
  Formatters::TableFormatter.clusters(table_data)
159
178
 
@@ -162,6 +181,23 @@ module LanguageOperator
162
181
  else
163
182
  puts "\nNo cluster selected. Use 'aictl use <cluster>' to select one."
164
183
  end
184
+
185
+ # Show helpful message if any clusters are not found
186
+ not_found_clusters = table_data.select { |c| c[:status] == 'Not Found' }
187
+ if not_found_clusters.any?
188
+ puts
189
+ Formatters::ProgressFormatter.warn('Some clusters exist in local config but not in Kubernetes')
190
+ puts
191
+ puts 'Clusters with "Not Found" status are defined in ~/.aictl/config.yaml'
192
+ puts 'but the corresponding LanguageCluster resource does not exist in Kubernetes.'
193
+ puts
194
+ puts 'To fix this:'
195
+ not_found_clusters.each do |cluster|
196
+ cluster_name = cluster[:name].gsub(' *', '')
197
+ puts " • Remove from config: aictl cluster delete #{cluster_name}"
198
+ puts " • Or recreate: aictl cluster create #{cluster_name}"
199
+ end
200
+ end
165
201
  rescue StandardError => e
166
202
  Formatters::ProgressFormatter.error("Failed to list clusters: #{e.message}")
167
203
  raise if ENV['DEBUG']
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'thor'
4
4
  require 'yaml'
5
- require 'pastel'
6
5
  require_relative '../formatters/progress_formatter'
7
6
  require_relative '../formatters/table_formatter'
8
7
  require_relative '../helpers/cluster_validator'
@@ -10,6 +9,7 @@ require_relative '../helpers/cluster_context'
10
9
  require_relative '../helpers/user_prompts'
11
10
  require_relative '../helpers/resource_dependency_checker'
12
11
  require_relative '../helpers/editor_helper'
12
+ require_relative '../helpers/pastel_helper'
13
13
  require_relative '../../config/cluster_config'
14
14
  require_relative '../../kubernetes/client'
15
15
  require_relative '../../kubernetes/resource_builder'
@@ -20,6 +20,7 @@ module LanguageOperator
20
20
  # Persona management commands
21
21
  class Persona < Thor
22
22
  include Helpers::ClusterValidator
23
+ include Helpers::PastelHelper
23
24
 
24
25
  desc 'list', 'List all personas in current cluster'
25
26
  option :cluster, type: :string, desc: 'Override current cluster context'
@@ -386,10 +387,6 @@ module LanguageOperator
386
387
  def edit_in_editor(content, filename_prefix)
387
388
  Helpers::EditorHelper.edit_content(content, filename_prefix, '.txt')
388
389
  end
389
-
390
- def pastel
391
- @pastel ||= Pastel.new
392
- end
393
390
  end
394
391
  end
395
392
  end
@@ -3,6 +3,8 @@
3
3
  require 'thor'
4
4
  require_relative '../formatters/progress_formatter'
5
5
  require_relative '../formatters/table_formatter'
6
+ require_relative '../formatters/status_formatter'
7
+ require_relative '../helpers/pastel_helper'
6
8
  require_relative '../../config/cluster_config'
7
9
  require_relative '../../kubernetes/client'
8
10
  require_relative '../helpers/cluster_validator'
@@ -12,13 +14,13 @@ module LanguageOperator
12
14
  module Commands
13
15
  # System status and overview command
14
16
  class Status < Thor
17
+ include Helpers::PastelHelper
18
+
15
19
  desc 'overview', 'Show system status and overview'
16
20
  def overview
17
21
  current_cluster = Config::ClusterConfig.current_cluster
18
22
  clusters = Config::ClusterConfig.list_clusters
19
23
 
20
- pastel = Pastel.new
21
-
22
24
  # Current cluster context
23
25
  if current_cluster
24
26
  cluster_config = Config::ClusterConfig.get_cluster(current_cluster)
@@ -124,22 +126,7 @@ module LanguageOperator
124
126
  private
125
127
 
126
128
  def format_status(status)
127
- require 'pastel'
128
- pastel = Pastel.new
129
-
130
- status_str = status.to_s
131
- case status_str.downcase
132
- when 'ready', 'running', 'active'
133
- "#{pastel.green('●')} #{status_str}"
134
- when 'pending', 'creating', 'synthesizing'
135
- "#{pastel.yellow('●')} #{status_str}"
136
- when 'failed', 'error'
137
- "#{pastel.red('●')} #{status_str}"
138
- when 'paused', 'stopped'
139
- "#{pastel.dim('●')} #{status_str}"
140
- else
141
- "#{pastel.dim('●')} #{status_str}"
142
- end
129
+ Formatters::StatusFormatter.format(status)
143
130
  end
144
131
 
145
132
  def categorize_by_status(resources)