language-operator 0.1.30 → 0.1.35

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -8
  3. data/CHANGELOG.md +49 -0
  4. data/CI_STATUS.md +56 -0
  5. data/Gemfile.lock +2 -2
  6. data/Makefile +28 -7
  7. data/Rakefile +29 -0
  8. data/docs/dsl/SCHEMA_VERSION.md +250 -0
  9. data/docs/dsl/agent-reference.md +13 -0
  10. data/lib/language_operator/agent/base.rb +10 -6
  11. data/lib/language_operator/agent/executor.rb +19 -97
  12. data/lib/language_operator/agent/safety/ast_validator.rb +62 -43
  13. data/lib/language_operator/agent/safety/safe_executor.rb +39 -2
  14. data/lib/language_operator/agent/scheduler.rb +60 -0
  15. data/lib/language_operator/agent/task_executor.rb +548 -0
  16. data/lib/language_operator/agent.rb +90 -27
  17. data/lib/language_operator/cli/base_command.rb +117 -0
  18. data/lib/language_operator/cli/commands/agent.rb +351 -466
  19. data/lib/language_operator/cli/commands/cluster.rb +276 -256
  20. data/lib/language_operator/cli/commands/install.rb +110 -119
  21. data/lib/language_operator/cli/commands/model.rb +284 -184
  22. data/lib/language_operator/cli/commands/persona.rb +220 -289
  23. data/lib/language_operator/cli/commands/quickstart.rb +4 -5
  24. data/lib/language_operator/cli/commands/status.rb +36 -53
  25. data/lib/language_operator/cli/commands/system.rb +760 -0
  26. data/lib/language_operator/cli/commands/tool.rb +356 -422
  27. data/lib/language_operator/cli/commands/use.rb +19 -22
  28. data/lib/language_operator/cli/formatters/code_formatter.rb +3 -7
  29. data/lib/language_operator/cli/formatters/log_formatter.rb +3 -5
  30. data/lib/language_operator/cli/formatters/progress_formatter.rb +3 -7
  31. data/lib/language_operator/cli/formatters/status_formatter.rb +37 -0
  32. data/lib/language_operator/cli/formatters/table_formatter.rb +10 -26
  33. data/lib/language_operator/cli/helpers/pastel_helper.rb +24 -0
  34. data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +0 -18
  35. data/lib/language_operator/cli/main.rb +4 -0
  36. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +0 -1
  37. data/lib/language_operator/client/config.rb +20 -21
  38. data/lib/language_operator/config.rb +115 -3
  39. data/lib/language_operator/constants.rb +54 -0
  40. data/lib/language_operator/dsl/agent_context.rb +7 -7
  41. data/lib/language_operator/dsl/agent_definition.rb +111 -26
  42. data/lib/language_operator/dsl/config.rb +30 -66
  43. data/lib/language_operator/dsl/main_definition.rb +114 -0
  44. data/lib/language_operator/dsl/schema.rb +1143 -0
  45. data/lib/language_operator/dsl/task_definition.rb +315 -0
  46. data/lib/language_operator/dsl.rb +1 -1
  47. data/lib/language_operator/instrumentation/task_tracer.rb +285 -0
  48. data/lib/language_operator/logger.rb +4 -4
  49. data/lib/language_operator/synthesis_test_harness.rb +324 -0
  50. data/lib/language_operator/templates/README.md +23 -0
  51. data/lib/language_operator/templates/examples/agent_synthesis.tmpl +133 -0
  52. data/lib/language_operator/templates/examples/persona_distillation.tmpl +19 -0
  53. data/lib/language_operator/templates/schema/.gitkeep +0 -0
  54. data/lib/language_operator/templates/schema/CHANGELOG.md +119 -0
  55. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +306 -0
  56. data/lib/language_operator/templates/schema/agent_dsl_schema.json +494 -0
  57. data/lib/language_operator/type_coercion.rb +250 -0
  58. data/lib/language_operator/ux/base.rb +81 -0
  59. data/lib/language_operator/ux/concerns/README.md +155 -0
  60. data/lib/language_operator/ux/concerns/headings.rb +90 -0
  61. data/lib/language_operator/ux/concerns/input_validation.rb +146 -0
  62. data/lib/language_operator/ux/concerns/provider_helpers.rb +167 -0
  63. data/lib/language_operator/ux/create_agent.rb +252 -0
  64. data/lib/language_operator/ux/create_model.rb +267 -0
  65. data/lib/language_operator/ux/quickstart.rb +594 -0
  66. data/lib/language_operator/version.rb +1 -1
  67. data/lib/language_operator.rb +2 -0
  68. data/requirements/ARCHITECTURE.md +1 -0
  69. data/requirements/SCRATCH.md +153 -0
  70. data/requirements/dsl.md +0 -0
  71. data/requirements/features +1 -0
  72. data/requirements/personas +1 -0
  73. data/requirements/proposals +1 -0
  74. data/requirements/tasks/iterate.md +14 -15
  75. data/requirements/tasks/optimize.md +13 -4
  76. data/synth/001/Makefile +90 -0
  77. data/synth/001/agent.rb +26 -0
  78. data/synth/001/agent.yaml +7 -0
  79. data/synth/001/output.log +44 -0
  80. data/synth/Makefile +39 -0
  81. data/synth/README.md +342 -0
  82. metadata +49 -18
  83. data/examples/README.md +0 -569
  84. data/examples/agent_example.rb +0 -86
  85. data/examples/chat_endpoint_agent.rb +0 -118
  86. data/examples/github_webhook_agent.rb +0 -171
  87. data/examples/mcp_agent.rb +0 -158
  88. data/examples/oauth_callback_agent.rb +0 -296
  89. data/examples/stripe_webhook_agent.rb +0 -219
  90. data/examples/webhook_agent.rb +0 -80
  91. data/lib/language_operator/dsl/workflow_definition.rb +0 -259
  92. data/test_agent_dsl.rb +0 -108
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'thor'
4
3
  require 'yaml'
4
+ require_relative '../base_command'
5
5
  require_relative '../formatters/progress_formatter'
6
6
  require_relative '../formatters/table_formatter'
7
7
  require_relative '../helpers/cluster_validator'
@@ -14,7 +14,9 @@ module LanguageOperator
14
14
  module CLI
15
15
  module Commands
16
16
  # Cluster management commands
17
- class Cluster < Thor
17
+ class Cluster < BaseCommand
18
+ include Helpers::PastelHelper
19
+
18
20
  desc 'create NAME', 'Create a new language cluster'
19
21
  option :namespace, type: :string, desc: 'Kubernetes namespace (defaults to current context namespace)'
20
22
  option :kubeconfig, type: :string, desc: 'Path to kubeconfig file'
@@ -22,312 +24,330 @@ module LanguageOperator
22
24
  option :switch, type: :boolean, default: true, desc: 'Switch to new cluster context'
23
25
  option :dry_run, type: :boolean, default: false, desc: 'Output the manifest without creating'
24
26
  def create(name)
25
- kubeconfig = options[:kubeconfig]
26
- context = options[:context]
27
-
28
- # Handle dry-run: output manifest and exit early
29
- if options[:dry_run]
30
- namespace = options[:namespace] || 'default'
31
- resource = Kubernetes::ResourceBuilder.language_cluster(name, namespace: namespace)
32
- puts resource.to_yaml
33
- return
34
- end
27
+ handle_command_error('create cluster') do
28
+ kubeconfig = options[:kubeconfig]
29
+ context = options[:context]
30
+
31
+ # Handle dry-run: output manifest and exit early
32
+ if options[:dry_run]
33
+ namespace = options[:namespace] || 'default'
34
+ resource = Kubernetes::ResourceBuilder.language_cluster(name, namespace: namespace)
35
+ puts resource.to_yaml
36
+ return
37
+ end
35
38
 
36
- # Check if cluster already exists
37
- if Config::ClusterConfig.cluster_exists?(name)
38
- Formatters::ProgressFormatter.error("Cluster '#{name}' already exists")
39
- exit 1
40
- end
39
+ # Check if cluster already exists
40
+ if Config::ClusterConfig.cluster_exists?(name)
41
+ Formatters::ProgressFormatter.error("Cluster '#{name}' already exists")
42
+ exit 1
43
+ end
41
44
 
42
- # Create Kubernetes client
43
- k8s = Formatters::ProgressFormatter.with_spinner('Connecting to Kubernetes cluster') do
44
- Kubernetes::Client.new(kubeconfig: kubeconfig, context: context)
45
- end
45
+ # Create Kubernetes client
46
+ k8s = Formatters::ProgressFormatter.with_spinner('Connecting to Kubernetes cluster') do
47
+ Kubernetes::Client.new(kubeconfig: kubeconfig, context: context)
48
+ end
46
49
 
47
- # Determine namespace: use --namespace flag, or current context namespace, or 'default'
48
- namespace = options[:namespace] || k8s.current_namespace || 'default'
50
+ # Determine namespace: use --namespace flag, or current context namespace, or 'default'
51
+ namespace = options[:namespace] || k8s.current_namespace || 'default'
49
52
 
50
- # Check if operator is installed
51
- unless k8s.operator_installed?
52
- Formatters::ProgressFormatter.error('Language Operator not found in cluster')
53
- puts "\nInstall the operator first:"
54
- puts ' aictl install'
55
- exit 1
56
- end
57
-
58
- # Create namespace if it doesn't exist
59
- unless k8s.namespace_exists?(namespace)
60
- Formatters::ProgressFormatter.with_spinner("Creating namespace '#{namespace}'") do
61
- k8s.create_namespace(namespace, labels: {
62
- 'app.kubernetes.io/managed-by' => 'aictl',
63
- 'langop.io/cluster' => name
64
- })
53
+ # Check if operator is installed
54
+ unless k8s.operator_installed?
55
+ Formatters::ProgressFormatter.error('Language Operator not found in cluster')
56
+ puts "\nInstall the operator first:"
57
+ puts ' aictl install'
58
+ exit 1
65
59
  end
66
- end
67
60
 
68
- # Create LanguageCluster resource
69
- Formatters::ProgressFormatter.with_spinner('Creating LanguageCluster resource') do
70
- resource = Kubernetes::ResourceBuilder.language_cluster(name, namespace: namespace)
71
- k8s.apply_resource(resource)
72
- resource
73
- end
61
+ # Create namespace if it doesn't exist
62
+ unless k8s.namespace_exists?(namespace)
63
+ Formatters::ProgressFormatter.with_spinner("Creating namespace '#{namespace}'") do
64
+ k8s.create_namespace(namespace, labels: {
65
+ 'app.kubernetes.io/managed-by' => 'aictl',
66
+ 'langop.io/cluster' => name
67
+ })
68
+ end
69
+ end
74
70
 
75
- # Get the actual Kubernetes context being used
76
- actual_context = k8s.current_context
77
-
78
- # Save cluster to config
79
- Formatters::ProgressFormatter.with_spinner('Saving cluster configuration') do
80
- Config::ClusterConfig.add_cluster(
81
- name,
82
- namespace,
83
- kubeconfig || ENV.fetch('KUBECONFIG', File.expand_path('~/.kube/config')),
84
- actual_context
85
- )
86
- end
71
+ # Create LanguageCluster resource
72
+ Formatters::ProgressFormatter.with_spinner('Creating LanguageCluster resource') do
73
+ resource = Kubernetes::ResourceBuilder.language_cluster(name, namespace: namespace)
74
+ k8s.apply_resource(resource)
75
+ resource
76
+ end
87
77
 
88
- # Switch to new cluster if requested
89
- if options[:switch]
90
- Config::ClusterConfig.set_current_cluster(name)
91
- Formatters::ProgressFormatter.success("Created and switched to cluster '#{name}'")
92
- else
93
- Formatters::ProgressFormatter.success("Created cluster '#{name}'")
94
- puts "\nSwitch to this cluster with:"
95
- puts " aictl use #{name}"
96
- end
78
+ # Get the actual Kubernetes context being used
79
+ actual_context = k8s.current_context
80
+
81
+ # Save cluster to config
82
+ Formatters::ProgressFormatter.with_spinner('Saving cluster configuration') do
83
+ Config::ClusterConfig.add_cluster(
84
+ name,
85
+ namespace,
86
+ kubeconfig || ENV.fetch('KUBECONFIG', File.expand_path('~/.kube/config')),
87
+ actual_context
88
+ )
89
+ end
97
90
 
98
- pastel = Pastel.new
99
- puts "\nCluster Details"
100
- puts '----------------'
101
- puts "Name: #{pastel.bold.white(name)}"
102
- puts "Namespace: #{pastel.bold.white(namespace)}"
103
- rescue StandardError => e
104
- Formatters::ProgressFormatter.error("Failed to create cluster: #{e.message}")
105
- raise if ENV['DEBUG']
91
+ # Switch to new cluster if requested
92
+ if options[:switch]
93
+ Config::ClusterConfig.set_current_cluster(name)
94
+ Formatters::ProgressFormatter.success("Created and switched to cluster '#{name}'")
95
+ else
96
+ Formatters::ProgressFormatter.success("Created cluster '#{name}'")
97
+ puts "\nSwitch to this cluster with:"
98
+ puts " aictl use #{name}"
99
+ end
106
100
 
107
- exit 1
101
+ puts "\nCluster Details"
102
+ puts '---------------'
103
+ puts "Name: #{pastel.bold.white(name)}"
104
+ puts "Namespace: #{pastel.bold.white(namespace)}"
105
+ puts "Context: #{pastel.bold.white(actual_context)}"
106
+ end
108
107
  end
109
108
 
110
109
  desc 'list', 'List all clusters'
111
110
  option :all, type: :boolean, default: false, desc: 'Show all clusters including inactive'
112
111
  def list
113
- clusters = Config::ClusterConfig.list_clusters
114
- current = Config::ClusterConfig.current_cluster
115
-
116
- if clusters.empty?
117
- Formatters::ProgressFormatter.info('No clusters found')
118
- puts "\nCreate a cluster with:"
119
- puts ' aictl cluster create <name>'
120
- return
121
- end
112
+ handle_command_error('list clusters') do
113
+ clusters = Config::ClusterConfig.list_clusters
114
+ current = Config::ClusterConfig.current_cluster
115
+
116
+ if clusters.empty?
117
+ Formatters::ProgressFormatter.info('No clusters found')
118
+ puts "\nCreate a cluster with:"
119
+ puts ' aictl cluster create <name>'
120
+ return
121
+ end
122
122
 
123
- # Build table data
124
- table_data = clusters.map do |cluster|
125
- k8s = Helpers::ClusterValidator.kubernetes_client(cluster[:name])
126
-
127
- # Get cluster stats
128
- agents = k8s.list_resources('LanguageAgent', namespace: cluster[:namespace])
129
- tools = k8s.list_resources('LanguageTool', namespace: cluster[:namespace])
130
- models = k8s.list_resources('LanguageModel', namespace: cluster[:namespace])
131
-
132
- # Get cluster status
133
- cluster_resource = k8s.get_resource('LanguageCluster', cluster[:name], cluster[:namespace])
134
- status = cluster_resource.dig('status', 'phase') || 'Unknown'
135
-
136
- name_display = cluster[:name]
137
- name_display += ' *' if cluster[:name] == current
138
-
139
- {
140
- name: name_display,
141
- namespace: cluster[:namespace],
142
- agents: agents.count,
143
- tools: tools.count,
144
- models: models.count,
145
- status: status
146
- }
147
- rescue StandardError
148
- {
149
- name: cluster[:name],
150
- namespace: cluster[:namespace],
151
- agents: '?',
152
- tools: '?',
153
- models: '?',
154
- status: 'Error'
155
- }
156
- end
123
+ # Build table data
124
+ table_data = clusters.map do |cluster|
125
+ k8s = Helpers::ClusterValidator.kubernetes_client(cluster[:name])
126
+
127
+ # Get cluster stats
128
+ agents = k8s.list_resources('LanguageAgent', namespace: cluster[:namespace])
129
+ tools = k8s.list_resources('LanguageTool', namespace: cluster[:namespace])
130
+ models = k8s.list_resources('LanguageModel', namespace: cluster[:namespace])
131
+
132
+ # Get cluster status
133
+ cluster_resource = k8s.get_resource('LanguageCluster', cluster[:name], cluster[:namespace])
134
+ status = cluster_resource.dig('status', 'phase') || 'Unknown'
135
+
136
+ name_display = cluster[:name]
137
+ name_display += ' *' if cluster[:name] == current
138
+
139
+ {
140
+ name: name_display,
141
+ namespace: cluster[:namespace],
142
+ agents: agents.count,
143
+ tools: tools.count,
144
+ models: models.count,
145
+ status: status
146
+ }
147
+ rescue K8s::Error::NotFound
148
+ # Cluster exists in local config but not in Kubernetes
149
+ name_display = cluster[:name]
150
+ name_display += ' *' if cluster[:name] == current
151
+
152
+ {
153
+ name: name_display,
154
+ namespace: cluster[:namespace],
155
+ agents: '-',
156
+ tools: '-',
157
+ models: '-',
158
+ status: 'Not Found'
159
+ }
160
+ rescue StandardError
161
+ # Other errors (connection issues, auth problems, etc.)
162
+ name_display = cluster[:name]
163
+ name_display += ' *' if cluster[:name] == current
164
+
165
+ {
166
+ name: name_display,
167
+ namespace: cluster[:namespace],
168
+ agents: '?',
169
+ tools: '?',
170
+ models: '?',
171
+ status: 'Error'
172
+ }
173
+ end
157
174
 
158
- Formatters::TableFormatter.clusters(table_data)
175
+ Formatters::TableFormatter.clusters(table_data)
159
176
 
160
- if current
161
- puts "\nCurrent cluster: #{current} (*)"
162
- else
163
- puts "\nNo cluster selected. Use 'aictl use <cluster>' to select one."
164
- end
165
- rescue StandardError => e
166
- Formatters::ProgressFormatter.error("Failed to list clusters: #{e.message}")
167
- raise if ENV['DEBUG']
177
+ if current
178
+ puts "\nCurrent cluster: #{current} (*)"
179
+ else
180
+ puts "\nNo cluster selected. Use 'aictl use <cluster>' to select one."
181
+ end
168
182
 
169
- exit 1
183
+ # Show helpful message if any clusters are not found
184
+ not_found_clusters = table_data.select { |c| c[:status] == 'Not Found' }
185
+ if not_found_clusters.any?
186
+ puts
187
+ Formatters::ProgressFormatter.warn('Some clusters exist in local config but not in Kubernetes')
188
+ puts
189
+ puts 'Clusters with "Not Found" status are defined in ~/.aictl/config.yaml'
190
+ puts 'but the corresponding LanguageCluster resource does not exist in Kubernetes.'
191
+ puts
192
+ puts 'To fix this:'
193
+ not_found_clusters.each do |cluster|
194
+ cluster_name = cluster[:name].gsub(' *', '')
195
+ puts " • Remove from config: aictl cluster delete #{cluster_name}"
196
+ puts " • Or recreate: aictl cluster create #{cluster_name}"
197
+ end
198
+ end
199
+ end
170
200
  end
171
201
 
172
202
  desc 'current', 'Show current cluster context'
173
203
  def current
174
- cluster_name = Config::ClusterConfig.current_cluster
175
-
176
- unless cluster_name
177
- Formatters::ProgressFormatter.info('No cluster selected')
178
- puts "\nSelect a cluster with:"
179
- puts ' aictl use <cluster>'
180
- return
181
- end
182
-
183
- cluster = Config::ClusterConfig.get_cluster(cluster_name)
184
-
185
- unless cluster
186
- Formatters::ProgressFormatter.error("Cluster '#{cluster_name}' not found in config")
187
- exit 1
188
- end
204
+ handle_command_error('show current cluster') do
205
+ cluster_name = Config::ClusterConfig.current_cluster
206
+
207
+ unless cluster_name
208
+ Formatters::ProgressFormatter.info('No cluster selected')
209
+ puts "\nSelect a cluster with:"
210
+ puts ' aictl use <cluster>'
211
+ return
212
+ end
189
213
 
190
- puts 'Current Cluster:'
191
- puts " Name: #{cluster[:name]}"
192
- puts " Namespace: #{cluster[:namespace]}"
193
- puts " Context: #{cluster[:context] || 'default'}"
194
- puts " Created: #{cluster[:created]}"
214
+ cluster = Config::ClusterConfig.get_cluster(cluster_name)
195
215
 
196
- # Check cluster health
197
- begin
198
- k8s = Helpers::ClusterValidator.kubernetes_client(cluster_name)
216
+ unless cluster
217
+ Formatters::ProgressFormatter.error("Cluster '#{cluster_name}' not found in config")
218
+ exit 1
219
+ end
199
220
 
200
- if k8s.operator_installed?
201
- version = k8s.operator_version
202
- Formatters::ProgressFormatter.success("Operator: #{version || 'installed'}")
203
- else
204
- Formatters::ProgressFormatter.warn('Operator: not found')
221
+ puts 'Current Cluster:'
222
+ puts " Name: #{cluster[:name]}"
223
+ puts " Namespace: #{cluster[:namespace]}"
224
+ puts " Context: #{cluster[:context] || 'default'}"
225
+ puts " Created: #{cluster[:created]}"
226
+
227
+ # Check cluster health
228
+ begin
229
+ k8s = Helpers::ClusterValidator.kubernetes_client(cluster_name)
230
+
231
+ if k8s.operator_installed?
232
+ version = k8s.operator_version
233
+ Formatters::ProgressFormatter.success("Operator: #{version || 'installed'}")
234
+ else
235
+ Formatters::ProgressFormatter.warn('Operator: not found')
236
+ end
237
+ rescue StandardError => e
238
+ Formatters::ProgressFormatter.error("Connection: #{e.message}")
205
239
  end
206
- rescue StandardError => e
207
- Formatters::ProgressFormatter.error("Connection: #{e.message}")
208
240
  end
209
- rescue StandardError => e
210
- Formatters::ProgressFormatter.error("Failed to show current cluster: #{e.message}")
211
- raise if ENV['DEBUG']
212
-
213
- exit 1
214
241
  end
215
242
 
216
243
  desc 'delete NAME', 'Delete a cluster'
217
244
  option :force, type: :boolean, default: false, desc: 'Skip confirmation'
218
245
  def delete(name)
219
- unless Config::ClusterConfig.cluster_exists?(name)
220
- Formatters::ProgressFormatter.error("Cluster '#{name}' not found")
221
- exit 1
222
- end
246
+ handle_command_error('delete cluster') do
247
+ unless Config::ClusterConfig.cluster_exists?(name)
248
+ Formatters::ProgressFormatter.error("Cluster '#{name}' not found")
249
+ exit 1
250
+ end
223
251
 
224
- cluster = Config::ClusterConfig.get_cluster(name)
252
+ cluster = Config::ClusterConfig.get_cluster(name)
225
253
 
226
- # Confirm deletion
227
- unless options[:force]
228
- pastel = Pastel.new
229
- puts "This will delete cluster #{pastel.bold.red(name)} and all its resources (agents, models, tools, personas)."
230
- puts
231
- return unless Helpers::UserPrompts.confirm('Are you sure?')
232
- end
254
+ # Confirm deletion
255
+ unless options[:force]
256
+ puts "This will delete cluster #{pastel.bold.red(name)} and all its resources (agents, models, tools, personas)."
257
+ puts
258
+ return unless Helpers::UserPrompts.confirm('Are you sure?')
259
+ end
233
260
 
234
- # Delete LanguageCluster resource
235
- begin
236
- k8s = Helpers::ClusterValidator.kubernetes_client(name)
261
+ # Delete LanguageCluster resource
262
+ begin
263
+ k8s = Helpers::ClusterValidator.kubernetes_client(name)
237
264
 
238
- Formatters::ProgressFormatter.with_spinner('Deleting LanguageCluster resource') do
239
- k8s.delete_resource('LanguageCluster', name, cluster[:namespace])
265
+ Formatters::ProgressFormatter.with_spinner('Deleting LanguageCluster resource') do
266
+ k8s.delete_resource('LanguageCluster', name, cluster[:namespace])
267
+ end
268
+ rescue StandardError => e
269
+ Formatters::ProgressFormatter.warn("Failed to delete cluster resource: #{e.message}")
240
270
  end
241
- rescue StandardError => e
242
- Formatters::ProgressFormatter.warn("Failed to delete cluster resource: #{e.message}")
243
- end
244
-
245
- # Remove from config
246
- Formatters::ProgressFormatter.with_spinner('Removing cluster from configuration') do
247
- Config::ClusterConfig.remove_cluster(name)
248
- end
249
271
 
250
- # Clear current cluster if this was it
251
- Config::ClusterConfig.set_current_cluster(nil) if Config::ClusterConfig.current_cluster == name
272
+ # Remove from config
273
+ Formatters::ProgressFormatter.with_spinner('Removing cluster from configuration') do
274
+ Config::ClusterConfig.remove_cluster(name)
275
+ end
252
276
 
253
- Formatters::ProgressFormatter.success("Deleted cluster '#{name}'")
254
- rescue StandardError => e
255
- Formatters::ProgressFormatter.error("Failed to delete cluster: #{e.message}")
256
- raise if ENV['DEBUG']
277
+ # Clear current cluster if this was it
278
+ Config::ClusterConfig.set_current_cluster(nil) if Config::ClusterConfig.current_cluster == name
257
279
 
258
- exit 1
280
+ Formatters::ProgressFormatter.success("Deleted cluster '#{name}'")
281
+ end
259
282
  end
260
283
 
261
284
  desc 'inspect NAME', 'Show detailed cluster information'
262
285
  def inspect(name)
263
- unless Config::ClusterConfig.cluster_exists?(name)
264
- Formatters::ProgressFormatter.error("Cluster '#{name}' not found")
265
- exit 1
266
- end
267
-
268
- cluster = Config::ClusterConfig.get_cluster(name)
269
-
270
- puts "Cluster: #{name}"
271
- puts " Namespace: #{cluster[:namespace]}"
272
- puts " Context: #{cluster[:context] || 'default'}"
273
- puts " Created: #{cluster[:created]}"
274
- puts
275
-
276
- # Get detailed cluster info
277
- begin
278
- k8s = Helpers::ClusterValidator.kubernetes_client(name)
279
-
280
- # Get cluster resource
281
- cluster_resource = k8s.get_resource('LanguageCluster', name, cluster[:namespace])
282
- status = cluster_resource.dig('status', 'phase') || 'Unknown'
283
-
284
- puts "Status: #{status}"
285
- puts
286
-
287
- # Get agents
288
- agents = k8s.list_resources('LanguageAgent', namespace: cluster[:namespace])
289
- puts "Agents: #{agents.count}"
290
- agents.each do |agent|
291
- agent_status = agent.dig('status', 'phase') || 'Unknown'
292
- puts " - #{agent.dig('metadata', 'name')} (#{agent_status})"
286
+ handle_command_error('inspect cluster') do
287
+ unless Config::ClusterConfig.cluster_exists?(name)
288
+ Formatters::ProgressFormatter.error("Cluster '#{name}' not found")
289
+ exit 1
293
290
  end
294
- puts
295
291
 
296
- # Get tools
297
- tools = k8s.list_resources('LanguageTool', namespace: cluster[:namespace])
298
- puts "Tools: #{tools.count}"
299
- tools.each do |tool|
300
- tool_type = tool.dig('spec', 'type')
301
- puts " - #{tool.dig('metadata', 'name')} (#{tool_type})"
302
- end
303
- puts
292
+ cluster = Config::ClusterConfig.get_cluster(name)
304
293
 
305
- # Get models
306
- models = k8s.list_resources('LanguageModel', namespace: cluster[:namespace])
307
- puts "Models: #{models.count}"
308
- models.each do |model|
309
- provider = model.dig('spec', 'provider')
310
- model_name = model.dig('spec', 'modelName')
311
- puts " - #{model.dig('metadata', 'name')} (#{provider}/#{model_name})"
312
- end
294
+ puts "Cluster: #{name}"
295
+ puts " Namespace: #{cluster[:namespace]}"
296
+ puts " Context: #{cluster[:context] || 'default'}"
297
+ puts " Created: #{cluster[:created]}"
313
298
  puts
314
299
 
315
- # Get personas
316
- personas = k8s.list_resources('LanguagePersona', namespace: cluster[:namespace])
317
- puts "Personas: #{personas.count}"
318
- personas.each do |persona|
319
- tone = persona.dig('spec', 'tone')
320
- puts " - #{persona.dig('metadata', 'name')} (#{tone})"
300
+ # Get detailed cluster info
301
+ begin
302
+ k8s = Helpers::ClusterValidator.kubernetes_client(name)
303
+
304
+ # Get cluster resource
305
+ cluster_resource = k8s.get_resource('LanguageCluster', name, cluster[:namespace])
306
+ status = cluster_resource.dig('status', 'phase') || 'Unknown'
307
+
308
+ puts "Status: #{status}"
309
+ puts
310
+
311
+ # Get agents
312
+ agents = k8s.list_resources('LanguageAgent', namespace: cluster[:namespace])
313
+ puts "Agents: #{agents.count}"
314
+ agents.each do |agent|
315
+ agent_status = agent.dig('status', 'phase') || 'Unknown'
316
+ puts " - #{agent.dig('metadata', 'name')} (#{agent_status})"
317
+ end
318
+ puts
319
+
320
+ # Get tools
321
+ tools = k8s.list_resources('LanguageTool', namespace: cluster[:namespace])
322
+ puts "Tools: #{tools.count}"
323
+ tools.each do |tool|
324
+ tool_type = tool.dig('spec', 'type')
325
+ puts " - #{tool.dig('metadata', 'name')} (#{tool_type})"
326
+ end
327
+ puts
328
+
329
+ # Get models
330
+ models = k8s.list_resources('LanguageModel', namespace: cluster[:namespace])
331
+ puts "Models: #{models.count}"
332
+ models.each do |model|
333
+ provider = model.dig('spec', 'provider')
334
+ model_name = model.dig('spec', 'modelName')
335
+ puts " - #{model.dig('metadata', 'name')} (#{provider}/#{model_name})"
336
+ end
337
+ puts
338
+
339
+ # Get personas
340
+ personas = k8s.list_resources('LanguagePersona', namespace: cluster[:namespace])
341
+ puts "Personas: #{personas.count}"
342
+ personas.each do |persona|
343
+ tone = persona.dig('spec', 'tone')
344
+ puts " - #{persona.dig('metadata', 'name')} (#{tone})"
345
+ end
346
+ rescue StandardError => e
347
+ Formatters::ProgressFormatter.error("Failed to get cluster details: #{e.message}")
348
+ raise if ENV['DEBUG']
321
349
  end
322
- rescue StandardError => e
323
- Formatters::ProgressFormatter.error("Failed to get cluster details: #{e.message}")
324
- raise if ENV['DEBUG']
325
350
  end
326
- rescue StandardError => e
327
- Formatters::ProgressFormatter.error("Failed to inspect cluster: #{e.message}")
328
- raise if ENV['DEBUG']
329
-
330
- exit 1
331
351
  end
332
352
  end
333
353
  end