language-operator 0.1.31 → 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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -8
  3. data/CHANGELOG.md +14 -0
  4. data/CI_STATUS.md +56 -0
  5. data/Gemfile.lock +2 -2
  6. data/Makefile +22 -6
  7. data/lib/language_operator/agent/base.rb +10 -6
  8. data/lib/language_operator/agent/executor.rb +19 -97
  9. data/lib/language_operator/agent/safety/ast_validator.rb +62 -43
  10. data/lib/language_operator/agent/safety/safe_executor.rb +27 -2
  11. data/lib/language_operator/agent/scheduler.rb +60 -0
  12. data/lib/language_operator/agent/task_executor.rb +548 -0
  13. data/lib/language_operator/agent.rb +90 -27
  14. data/lib/language_operator/cli/base_command.rb +117 -0
  15. data/lib/language_operator/cli/commands/agent.rb +339 -407
  16. data/lib/language_operator/cli/commands/cluster.rb +274 -290
  17. data/lib/language_operator/cli/commands/install.rb +110 -119
  18. data/lib/language_operator/cli/commands/model.rb +284 -184
  19. data/lib/language_operator/cli/commands/persona.rb +218 -284
  20. data/lib/language_operator/cli/commands/quickstart.rb +4 -5
  21. data/lib/language_operator/cli/commands/status.rb +31 -35
  22. data/lib/language_operator/cli/commands/system.rb +221 -233
  23. data/lib/language_operator/cli/commands/tool.rb +356 -422
  24. data/lib/language_operator/cli/commands/use.rb +19 -22
  25. data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +0 -18
  26. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +0 -1
  27. data/lib/language_operator/client/config.rb +20 -21
  28. data/lib/language_operator/config.rb +115 -3
  29. data/lib/language_operator/constants.rb +54 -0
  30. data/lib/language_operator/dsl/agent_context.rb +7 -7
  31. data/lib/language_operator/dsl/agent_definition.rb +111 -26
  32. data/lib/language_operator/dsl/config.rb +30 -66
  33. data/lib/language_operator/dsl/main_definition.rb +114 -0
  34. data/lib/language_operator/dsl/schema.rb +84 -43
  35. data/lib/language_operator/dsl/task_definition.rb +315 -0
  36. data/lib/language_operator/dsl.rb +0 -1
  37. data/lib/language_operator/instrumentation/task_tracer.rb +285 -0
  38. data/lib/language_operator/logger.rb +4 -4
  39. data/lib/language_operator/synthesis_test_harness.rb +324 -0
  40. data/lib/language_operator/templates/examples/agent_synthesis.tmpl +26 -8
  41. data/lib/language_operator/templates/schema/CHANGELOG.md +26 -0
  42. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
  43. data/lib/language_operator/templates/schema/agent_dsl_schema.json +84 -42
  44. data/lib/language_operator/type_coercion.rb +250 -0
  45. data/lib/language_operator/ux/base.rb +81 -0
  46. data/lib/language_operator/ux/concerns/README.md +155 -0
  47. data/lib/language_operator/ux/concerns/headings.rb +90 -0
  48. data/lib/language_operator/ux/concerns/input_validation.rb +146 -0
  49. data/lib/language_operator/ux/concerns/provider_helpers.rb +167 -0
  50. data/lib/language_operator/ux/create_agent.rb +252 -0
  51. data/lib/language_operator/ux/create_model.rb +267 -0
  52. data/lib/language_operator/ux/quickstart.rb +594 -0
  53. data/lib/language_operator/version.rb +1 -1
  54. data/lib/language_operator.rb +2 -0
  55. data/requirements/ARCHITECTURE.md +1 -0
  56. data/requirements/SCRATCH.md +153 -0
  57. data/requirements/dsl.md +0 -0
  58. data/requirements/features +1 -0
  59. data/requirements/personas +1 -0
  60. data/requirements/proposals +1 -0
  61. data/requirements/tasks/iterate.md +14 -15
  62. data/requirements/tasks/optimize.md +13 -4
  63. data/synth/001/Makefile +90 -0
  64. data/synth/001/agent.rb +26 -0
  65. data/synth/001/agent.yaml +7 -0
  66. data/synth/001/output.log +44 -0
  67. data/synth/Makefile +39 -0
  68. data/synth/README.md +342 -0
  69. metadata +37 -10
  70. data/lib/language_operator/dsl/workflow_definition.rb +0 -259
  71. 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,348 +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
35
-
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
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
41
38
 
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
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
46
44
 
47
- # Determine namespace: use --namespace flag, or current context namespace, or 'default'
48
- namespace = options[:namespace] || k8s.current_namespace || 'default'
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
49
49
 
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
50
+ # Determine namespace: use --namespace flag, or current context namespace, or 'default'
51
+ namespace = options[:namespace] || k8s.current_namespace || 'default'
57
52
 
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
- # rubocop:disable Metrics/BlockLength
125
- table_data = clusters.map do |cluster|
126
- k8s = Helpers::ClusterValidator.kubernetes_client(cluster[:name])
127
-
128
- # Get cluster stats
129
- agents = k8s.list_resources('LanguageAgent', namespace: cluster[:namespace])
130
- tools = k8s.list_resources('LanguageTool', namespace: cluster[:namespace])
131
- models = k8s.list_resources('LanguageModel', namespace: cluster[:namespace])
132
-
133
- # Get cluster status
134
- cluster_resource = k8s.get_resource('LanguageCluster', cluster[:name], cluster[:namespace])
135
- status = cluster_resource.dig('status', 'phase') || 'Unknown'
136
-
137
- name_display = cluster[:name]
138
- name_display += ' *' if cluster[:name] == current
139
-
140
- {
141
- name: name_display,
142
- namespace: cluster[:namespace],
143
- agents: agents.count,
144
- tools: tools.count,
145
- models: models.count,
146
- status: status
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
- }
161
- rescue StandardError
162
- # Other errors (connection issues, auth problems, etc.)
163
- name_display = cluster[:name]
164
- name_display += ' *' if cluster[:name] == current
165
-
166
- {
167
- name: name_display,
168
- namespace: cluster[:namespace],
169
- agents: '?',
170
- tools: '?',
171
- models: '?',
172
- status: 'Error'
173
- }
174
- end
175
- # rubocop:enable Metrics/BlockLength
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
176
174
 
177
- Formatters::TableFormatter.clusters(table_data)
175
+ Formatters::TableFormatter.clusters(table_data)
178
176
 
179
- if current
180
- puts "\nCurrent cluster: #{current} (*)"
181
- else
182
- puts "\nNo cluster selected. Use 'aictl use <cluster>' to select one."
183
- end
177
+ if current
178
+ puts "\nCurrent cluster: #{current} (*)"
179
+ else
180
+ puts "\nNo cluster selected. Use 'aictl use <cluster>' to select one."
181
+ end
184
182
 
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}"
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
199
198
  end
200
199
  end
201
- rescue StandardError => e
202
- Formatters::ProgressFormatter.error("Failed to list clusters: #{e.message}")
203
- raise if ENV['DEBUG']
204
-
205
- exit 1
206
200
  end
207
201
 
208
202
  desc 'current', 'Show current cluster context'
209
203
  def current
210
- cluster_name = Config::ClusterConfig.current_cluster
211
-
212
- unless cluster_name
213
- Formatters::ProgressFormatter.info('No cluster selected')
214
- puts "\nSelect a cluster with:"
215
- puts ' aictl use <cluster>'
216
- return
217
- end
218
-
219
- cluster = Config::ClusterConfig.get_cluster(cluster_name)
220
-
221
- unless cluster
222
- Formatters::ProgressFormatter.error("Cluster '#{cluster_name}' not found in config")
223
- exit 1
224
- 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
225
213
 
226
- puts 'Current Cluster:'
227
- puts " Name: #{cluster[:name]}"
228
- puts " Namespace: #{cluster[:namespace]}"
229
- puts " Context: #{cluster[:context] || 'default'}"
230
- puts " Created: #{cluster[:created]}"
214
+ cluster = Config::ClusterConfig.get_cluster(cluster_name)
231
215
 
232
- # Check cluster health
233
- begin
234
- 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
235
220
 
236
- if k8s.operator_installed?
237
- version = k8s.operator_version
238
- Formatters::ProgressFormatter.success("Operator: #{version || 'installed'}")
239
- else
240
- 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}")
241
239
  end
242
- rescue StandardError => e
243
- Formatters::ProgressFormatter.error("Connection: #{e.message}")
244
240
  end
245
- rescue StandardError => e
246
- Formatters::ProgressFormatter.error("Failed to show current cluster: #{e.message}")
247
- raise if ENV['DEBUG']
248
-
249
- exit 1
250
241
  end
251
242
 
252
243
  desc 'delete NAME', 'Delete a cluster'
253
244
  option :force, type: :boolean, default: false, desc: 'Skip confirmation'
254
245
  def delete(name)
255
- unless Config::ClusterConfig.cluster_exists?(name)
256
- Formatters::ProgressFormatter.error("Cluster '#{name}' not found")
257
- exit 1
258
- 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
259
251
 
260
- cluster = Config::ClusterConfig.get_cluster(name)
252
+ cluster = Config::ClusterConfig.get_cluster(name)
261
253
 
262
- # Confirm deletion
263
- unless options[:force]
264
- pastel = Pastel.new
265
- puts "This will delete cluster #{pastel.bold.red(name)} and all its resources (agents, models, tools, personas)."
266
- puts
267
- return unless Helpers::UserPrompts.confirm('Are you sure?')
268
- 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
269
260
 
270
- # Delete LanguageCluster resource
271
- begin
272
- k8s = Helpers::ClusterValidator.kubernetes_client(name)
261
+ # Delete LanguageCluster resource
262
+ begin
263
+ k8s = Helpers::ClusterValidator.kubernetes_client(name)
273
264
 
274
- Formatters::ProgressFormatter.with_spinner('Deleting LanguageCluster resource') do
275
- 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}")
276
270
  end
277
- rescue StandardError => e
278
- Formatters::ProgressFormatter.warn("Failed to delete cluster resource: #{e.message}")
279
- end
280
-
281
- # Remove from config
282
- Formatters::ProgressFormatter.with_spinner('Removing cluster from configuration') do
283
- Config::ClusterConfig.remove_cluster(name)
284
- end
285
271
 
286
- # Clear current cluster if this was it
287
- 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
288
276
 
289
- Formatters::ProgressFormatter.success("Deleted cluster '#{name}'")
290
- rescue StandardError => e
291
- Formatters::ProgressFormatter.error("Failed to delete cluster: #{e.message}")
292
- 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
293
279
 
294
- exit 1
280
+ Formatters::ProgressFormatter.success("Deleted cluster '#{name}'")
281
+ end
295
282
  end
296
283
 
297
284
  desc 'inspect NAME', 'Show detailed cluster information'
298
285
  def inspect(name)
299
- unless Config::ClusterConfig.cluster_exists?(name)
300
- Formatters::ProgressFormatter.error("Cluster '#{name}' not found")
301
- exit 1
302
- end
303
-
304
- cluster = Config::ClusterConfig.get_cluster(name)
305
-
306
- puts "Cluster: #{name}"
307
- puts " Namespace: #{cluster[:namespace]}"
308
- puts " Context: #{cluster[:context] || 'default'}"
309
- puts " Created: #{cluster[:created]}"
310
- puts
311
-
312
- # Get detailed cluster info
313
- begin
314
- k8s = Helpers::ClusterValidator.kubernetes_client(name)
315
-
316
- # Get cluster resource
317
- cluster_resource = k8s.get_resource('LanguageCluster', name, cluster[:namespace])
318
- status = cluster_resource.dig('status', 'phase') || 'Unknown'
319
-
320
- puts "Status: #{status}"
321
- puts
322
-
323
- # Get agents
324
- agents = k8s.list_resources('LanguageAgent', namespace: cluster[:namespace])
325
- puts "Agents: #{agents.count}"
326
- agents.each do |agent|
327
- agent_status = agent.dig('status', 'phase') || 'Unknown'
328
- 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
329
290
  end
330
- puts
331
291
 
332
- # Get tools
333
- tools = k8s.list_resources('LanguageTool', namespace: cluster[:namespace])
334
- puts "Tools: #{tools.count}"
335
- tools.each do |tool|
336
- tool_type = tool.dig('spec', 'type')
337
- puts " - #{tool.dig('metadata', 'name')} (#{tool_type})"
338
- end
339
- puts
292
+ cluster = Config::ClusterConfig.get_cluster(name)
340
293
 
341
- # Get models
342
- models = k8s.list_resources('LanguageModel', namespace: cluster[:namespace])
343
- puts "Models: #{models.count}"
344
- models.each do |model|
345
- provider = model.dig('spec', 'provider')
346
- model_name = model.dig('spec', 'modelName')
347
- puts " - #{model.dig('metadata', 'name')} (#{provider}/#{model_name})"
348
- end
294
+ puts "Cluster: #{name}"
295
+ puts " Namespace: #{cluster[:namespace]}"
296
+ puts " Context: #{cluster[:context] || 'default'}"
297
+ puts " Created: #{cluster[:created]}"
349
298
  puts
350
299
 
351
- # Get personas
352
- personas = k8s.list_resources('LanguagePersona', namespace: cluster[:namespace])
353
- puts "Personas: #{personas.count}"
354
- personas.each do |persona|
355
- tone = persona.dig('spec', 'tone')
356
- 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']
357
349
  end
358
- rescue StandardError => e
359
- Formatters::ProgressFormatter.error("Failed to get cluster details: #{e.message}")
360
- raise if ENV['DEBUG']
361
350
  end
362
- rescue StandardError => e
363
- Formatters::ProgressFormatter.error("Failed to inspect cluster: #{e.message}")
364
- raise if ENV['DEBUG']
365
-
366
- exit 1
367
351
  end
368
352
  end
369
353
  end