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.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -8
- data/CHANGELOG.md +14 -0
- data/CI_STATUS.md +56 -0
- data/Gemfile.lock +2 -2
- data/Makefile +22 -6
- data/lib/language_operator/agent/base.rb +10 -6
- data/lib/language_operator/agent/executor.rb +19 -97
- data/lib/language_operator/agent/safety/ast_validator.rb +62 -43
- data/lib/language_operator/agent/safety/safe_executor.rb +27 -2
- data/lib/language_operator/agent/scheduler.rb +60 -0
- data/lib/language_operator/agent/task_executor.rb +548 -0
- data/lib/language_operator/agent.rb +90 -27
- data/lib/language_operator/cli/base_command.rb +117 -0
- data/lib/language_operator/cli/commands/agent.rb +339 -407
- data/lib/language_operator/cli/commands/cluster.rb +274 -290
- data/lib/language_operator/cli/commands/install.rb +110 -119
- data/lib/language_operator/cli/commands/model.rb +284 -184
- data/lib/language_operator/cli/commands/persona.rb +218 -284
- data/lib/language_operator/cli/commands/quickstart.rb +4 -5
- data/lib/language_operator/cli/commands/status.rb +31 -35
- data/lib/language_operator/cli/commands/system.rb +221 -233
- data/lib/language_operator/cli/commands/tool.rb +356 -422
- data/lib/language_operator/cli/commands/use.rb +19 -22
- data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +0 -18
- data/lib/language_operator/cli/wizards/quickstart_wizard.rb +0 -1
- data/lib/language_operator/client/config.rb +20 -21
- data/lib/language_operator/config.rb +115 -3
- data/lib/language_operator/constants.rb +54 -0
- data/lib/language_operator/dsl/agent_context.rb +7 -7
- data/lib/language_operator/dsl/agent_definition.rb +111 -26
- data/lib/language_operator/dsl/config.rb +30 -66
- data/lib/language_operator/dsl/main_definition.rb +114 -0
- data/lib/language_operator/dsl/schema.rb +84 -43
- data/lib/language_operator/dsl/task_definition.rb +315 -0
- data/lib/language_operator/dsl.rb +0 -1
- data/lib/language_operator/instrumentation/task_tracer.rb +285 -0
- data/lib/language_operator/logger.rb +4 -4
- data/lib/language_operator/synthesis_test_harness.rb +324 -0
- data/lib/language_operator/templates/examples/agent_synthesis.tmpl +26 -8
- data/lib/language_operator/templates/schema/CHANGELOG.md +26 -0
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +84 -42
- data/lib/language_operator/type_coercion.rb +250 -0
- data/lib/language_operator/ux/base.rb +81 -0
- data/lib/language_operator/ux/concerns/README.md +155 -0
- data/lib/language_operator/ux/concerns/headings.rb +90 -0
- data/lib/language_operator/ux/concerns/input_validation.rb +146 -0
- data/lib/language_operator/ux/concerns/provider_helpers.rb +167 -0
- data/lib/language_operator/ux/create_agent.rb +252 -0
- data/lib/language_operator/ux/create_model.rb +267 -0
- data/lib/language_operator/ux/quickstart.rb +594 -0
- data/lib/language_operator/version.rb +1 -1
- data/lib/language_operator.rb +2 -0
- data/requirements/ARCHITECTURE.md +1 -0
- data/requirements/SCRATCH.md +153 -0
- data/requirements/dsl.md +0 -0
- data/requirements/features +1 -0
- data/requirements/personas +1 -0
- data/requirements/proposals +1 -0
- data/requirements/tasks/iterate.md +14 -15
- data/requirements/tasks/optimize.md +13 -4
- data/synth/001/Makefile +90 -0
- data/synth/001/agent.rb +26 -0
- data/synth/001/agent.yaml +7 -0
- data/synth/001/output.log +44 -0
- data/synth/Makefile +39 -0
- data/synth/README.md +342 -0
- metadata +37 -10
- data/lib/language_operator/dsl/workflow_definition.rb +0 -259
- 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 <
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
175
|
+
Formatters::TableFormatter.clusters(table_data)
|
|
178
176
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
216
|
+
unless cluster
|
|
217
|
+
Formatters::ProgressFormatter.error("Cluster '#{cluster_name}' not found in config")
|
|
218
|
+
exit 1
|
|
219
|
+
end
|
|
235
220
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
252
|
+
cluster = Config::ClusterConfig.get_cluster(name)
|
|
261
253
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
261
|
+
# Delete LanguageCluster resource
|
|
262
|
+
begin
|
|
263
|
+
k8s = Helpers::ClusterValidator.kubernetes_client(name)
|
|
273
264
|
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
287
|
-
|
|
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
|
-
|
|
290
|
-
|
|
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
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
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
|
-
|
|
342
|
-
|
|
343
|
-
puts "
|
|
344
|
-
|
|
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
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|