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.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -8
- data/CHANGELOG.md +49 -0
- data/CI_STATUS.md +56 -0
- data/Gemfile.lock +2 -2
- data/Makefile +28 -7
- data/Rakefile +29 -0
- data/docs/dsl/SCHEMA_VERSION.md +250 -0
- data/docs/dsl/agent-reference.md +13 -0
- 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 +39 -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 +351 -466
- data/lib/language_operator/cli/commands/cluster.rb +276 -256
- 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 +220 -289
- data/lib/language_operator/cli/commands/quickstart.rb +4 -5
- data/lib/language_operator/cli/commands/status.rb +36 -53
- data/lib/language_operator/cli/commands/system.rb +760 -0
- 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/formatters/code_formatter.rb +3 -7
- data/lib/language_operator/cli/formatters/log_formatter.rb +3 -5
- data/lib/language_operator/cli/formatters/progress_formatter.rb +3 -7
- data/lib/language_operator/cli/formatters/status_formatter.rb +37 -0
- data/lib/language_operator/cli/formatters/table_formatter.rb +10 -26
- data/lib/language_operator/cli/helpers/pastel_helper.rb +24 -0
- data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +0 -18
- data/lib/language_operator/cli/main.rb +4 -0
- 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 +1143 -0
- data/lib/language_operator/dsl/task_definition.rb +315 -0
- data/lib/language_operator/dsl.rb +1 -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/README.md +23 -0
- data/lib/language_operator/templates/examples/agent_synthesis.tmpl +133 -0
- data/lib/language_operator/templates/examples/persona_distillation.tmpl +19 -0
- data/lib/language_operator/templates/schema/.gitkeep +0 -0
- data/lib/language_operator/templates/schema/CHANGELOG.md +119 -0
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +306 -0
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +494 -0
- 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 +49 -18
- data/examples/README.md +0 -569
- data/examples/agent_example.rb +0 -86
- data/examples/chat_endpoint_agent.rb +0 -118
- data/examples/github_webhook_agent.rb +0 -171
- data/examples/mcp_agent.rb +0 -158
- data/examples/oauth_callback_agent.rb +0 -296
- data/examples/stripe_webhook_agent.rb +0 -219
- data/examples/webhook_agent.rb +0 -80
- 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,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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
48
|
-
|
|
50
|
+
# Determine namespace: use --namespace flag, or current context namespace, or 'default'
|
|
51
|
+
namespace = options[:namespace] || k8s.current_namespace || 'default'
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
175
|
+
Formatters::TableFormatter.clusters(table_data)
|
|
159
176
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
216
|
+
unless cluster
|
|
217
|
+
Formatters::ProgressFormatter.error("Cluster '#{cluster_name}' not found in config")
|
|
218
|
+
exit 1
|
|
219
|
+
end
|
|
199
220
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
252
|
+
cluster = Config::ClusterConfig.get_cluster(name)
|
|
225
253
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
261
|
+
# Delete LanguageCluster resource
|
|
262
|
+
begin
|
|
263
|
+
k8s = Helpers::ClusterValidator.kubernetes_client(name)
|
|
237
264
|
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
251
|
-
|
|
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
|
-
|
|
254
|
-
|
|
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
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
puts "
|
|
308
|
-
|
|
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
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|