language-operator 0.0.1 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +125 -0
- data/CHANGELOG.md +88 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +284 -0
- data/LICENSE +229 -21
- data/Makefile +82 -0
- data/README.md +3 -11
- data/Rakefile +63 -0
- data/bin/aictl +7 -0
- data/completions/_aictl +232 -0
- data/completions/aictl.bash +121 -0
- data/completions/aictl.fish +114 -0
- data/docs/architecture/agent-runtime.md +585 -0
- data/docs/dsl/SCHEMA_VERSION.md +250 -0
- data/docs/dsl/agent-reference.md +604 -0
- data/docs/dsl/best-practices.md +1078 -0
- data/docs/dsl/chat-endpoints.md +895 -0
- data/docs/dsl/constraints.md +671 -0
- data/docs/dsl/mcp-integration.md +1177 -0
- data/docs/dsl/webhooks.md +932 -0
- data/docs/dsl/workflows.md +744 -0
- data/lib/language_operator/agent/base.rb +110 -0
- data/lib/language_operator/agent/executor.rb +440 -0
- data/lib/language_operator/agent/instrumentation.rb +54 -0
- data/lib/language_operator/agent/metrics_tracker.rb +183 -0
- data/lib/language_operator/agent/safety/ast_validator.rb +272 -0
- data/lib/language_operator/agent/safety/audit_logger.rb +104 -0
- data/lib/language_operator/agent/safety/budget_tracker.rb +175 -0
- data/lib/language_operator/agent/safety/content_filter.rb +93 -0
- data/lib/language_operator/agent/safety/manager.rb +207 -0
- data/lib/language_operator/agent/safety/rate_limiter.rb +150 -0
- data/lib/language_operator/agent/safety/safe_executor.rb +127 -0
- data/lib/language_operator/agent/scheduler.rb +183 -0
- data/lib/language_operator/agent/telemetry.rb +116 -0
- data/lib/language_operator/agent/web_server.rb +610 -0
- data/lib/language_operator/agent/webhook_authenticator.rb +226 -0
- data/lib/language_operator/agent.rb +149 -0
- data/lib/language_operator/cli/commands/agent.rb +1205 -0
- data/lib/language_operator/cli/commands/cluster.rb +371 -0
- data/lib/language_operator/cli/commands/install.rb +404 -0
- data/lib/language_operator/cli/commands/model.rb +266 -0
- data/lib/language_operator/cli/commands/persona.rb +393 -0
- data/lib/language_operator/cli/commands/quickstart.rb +22 -0
- data/lib/language_operator/cli/commands/status.rb +143 -0
- data/lib/language_operator/cli/commands/system.rb +772 -0
- data/lib/language_operator/cli/commands/tool.rb +537 -0
- data/lib/language_operator/cli/commands/use.rb +47 -0
- data/lib/language_operator/cli/errors/handler.rb +180 -0
- data/lib/language_operator/cli/errors/suggestions.rb +176 -0
- data/lib/language_operator/cli/formatters/code_formatter.rb +77 -0
- data/lib/language_operator/cli/formatters/log_formatter.rb +288 -0
- data/lib/language_operator/cli/formatters/progress_formatter.rb +49 -0
- data/lib/language_operator/cli/formatters/status_formatter.rb +37 -0
- data/lib/language_operator/cli/formatters/table_formatter.rb +163 -0
- data/lib/language_operator/cli/formatters/value_formatter.rb +113 -0
- data/lib/language_operator/cli/helpers/cluster_context.rb +62 -0
- data/lib/language_operator/cli/helpers/cluster_validator.rb +101 -0
- data/lib/language_operator/cli/helpers/editor_helper.rb +58 -0
- data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +167 -0
- data/lib/language_operator/cli/helpers/pastel_helper.rb +24 -0
- data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +74 -0
- data/lib/language_operator/cli/helpers/schedule_builder.rb +108 -0
- data/lib/language_operator/cli/helpers/user_prompts.rb +69 -0
- data/lib/language_operator/cli/main.rb +236 -0
- data/lib/language_operator/cli/templates/tools/generic.yaml +66 -0
- data/lib/language_operator/cli/wizards/agent_wizard.rb +246 -0
- data/lib/language_operator/cli/wizards/quickstart_wizard.rb +588 -0
- data/lib/language_operator/client/base.rb +214 -0
- data/lib/language_operator/client/config.rb +136 -0
- data/lib/language_operator/client/cost_calculator.rb +37 -0
- data/lib/language_operator/client/mcp_connector.rb +123 -0
- data/lib/language_operator/client.rb +19 -0
- data/lib/language_operator/config/cluster_config.rb +101 -0
- data/lib/language_operator/config/tool_patterns.yaml +57 -0
- data/lib/language_operator/config/tool_registry.rb +96 -0
- data/lib/language_operator/config.rb +138 -0
- data/lib/language_operator/dsl/adapter.rb +124 -0
- data/lib/language_operator/dsl/agent_context.rb +90 -0
- data/lib/language_operator/dsl/agent_definition.rb +427 -0
- data/lib/language_operator/dsl/chat_endpoint_definition.rb +115 -0
- data/lib/language_operator/dsl/config.rb +119 -0
- data/lib/language_operator/dsl/context.rb +50 -0
- data/lib/language_operator/dsl/execution_context.rb +47 -0
- data/lib/language_operator/dsl/helpers.rb +109 -0
- data/lib/language_operator/dsl/http.rb +184 -0
- data/lib/language_operator/dsl/mcp_server_definition.rb +73 -0
- data/lib/language_operator/dsl/parameter_definition.rb +124 -0
- data/lib/language_operator/dsl/registry.rb +36 -0
- data/lib/language_operator/dsl/schema.rb +1102 -0
- data/lib/language_operator/dsl/shell.rb +125 -0
- data/lib/language_operator/dsl/tool_definition.rb +112 -0
- data/lib/language_operator/dsl/webhook_authentication.rb +114 -0
- data/lib/language_operator/dsl/webhook_definition.rb +106 -0
- data/lib/language_operator/dsl/workflow_definition.rb +259 -0
- data/lib/language_operator/dsl.rb +161 -0
- data/lib/language_operator/errors.rb +60 -0
- data/lib/language_operator/kubernetes/client.rb +279 -0
- data/lib/language_operator/kubernetes/resource_builder.rb +194 -0
- data/lib/language_operator/loggable.rb +47 -0
- data/lib/language_operator/logger.rb +141 -0
- data/lib/language_operator/retry.rb +123 -0
- data/lib/language_operator/retryable.rb +132 -0
- data/lib/language_operator/templates/README.md +23 -0
- data/lib/language_operator/templates/examples/agent_synthesis.tmpl +115 -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 +93 -0
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +306 -0
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +452 -0
- data/lib/language_operator/tool_loader.rb +242 -0
- data/lib/language_operator/validators.rb +170 -0
- data/lib/language_operator/version.rb +1 -1
- data/lib/language_operator.rb +65 -3
- data/requirements/tasks/challenge.md +9 -0
- data/requirements/tasks/iterate.md +36 -0
- data/requirements/tasks/optimize.md +21 -0
- data/requirements/tasks/tag.md +5 -0
- data/test_agent_dsl.rb +108 -0
- metadata +507 -20
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
require 'yaml'
|
|
5
|
+
require_relative '../formatters/progress_formatter'
|
|
6
|
+
require_relative '../formatters/table_formatter'
|
|
7
|
+
require_relative '../helpers/cluster_validator'
|
|
8
|
+
require_relative '../helpers/cluster_context'
|
|
9
|
+
require_relative '../helpers/user_prompts'
|
|
10
|
+
require_relative '../helpers/resource_dependency_checker'
|
|
11
|
+
require_relative '../helpers/editor_helper'
|
|
12
|
+
require_relative '../helpers/pastel_helper'
|
|
13
|
+
require_relative '../../config/cluster_config'
|
|
14
|
+
require_relative '../../kubernetes/client'
|
|
15
|
+
require_relative '../../kubernetes/resource_builder'
|
|
16
|
+
|
|
17
|
+
module LanguageOperator
|
|
18
|
+
module CLI
|
|
19
|
+
module Commands
|
|
20
|
+
# Persona management commands
|
|
21
|
+
class Persona < Thor
|
|
22
|
+
include Helpers::ClusterValidator
|
|
23
|
+
include Helpers::PastelHelper
|
|
24
|
+
|
|
25
|
+
desc 'list', 'List all personas in current cluster'
|
|
26
|
+
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
27
|
+
def list
|
|
28
|
+
ctx = Helpers::ClusterContext.from_options(options)
|
|
29
|
+
|
|
30
|
+
personas = ctx.client.list_resources('LanguagePersona', namespace: ctx.namespace)
|
|
31
|
+
|
|
32
|
+
if personas.empty?
|
|
33
|
+
Formatters::ProgressFormatter.info("No personas found in cluster '#{ctx.name}'")
|
|
34
|
+
puts
|
|
35
|
+
puts 'Personas define the personality and capabilities of agents.'
|
|
36
|
+
puts
|
|
37
|
+
puts 'Create a persona with:'
|
|
38
|
+
puts ' aictl persona create <name>'
|
|
39
|
+
return
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Get agents to count usage
|
|
43
|
+
agents = ctx.client.list_resources('LanguageAgent', namespace: ctx.namespace)
|
|
44
|
+
|
|
45
|
+
table_data = personas.map do |persona|
|
|
46
|
+
name = persona.dig('metadata', 'name')
|
|
47
|
+
used_by = agents.count { |a| a.dig('spec', 'persona') == name }
|
|
48
|
+
|
|
49
|
+
{
|
|
50
|
+
name: name,
|
|
51
|
+
tone: persona.dig('spec', 'tone') || 'neutral',
|
|
52
|
+
used_by: used_by,
|
|
53
|
+
description: persona.dig('spec', 'description') || ''
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
Formatters::TableFormatter.personas(table_data)
|
|
58
|
+
rescue StandardError => e
|
|
59
|
+
Formatters::ProgressFormatter.error("Failed to list personas: #{e.message}")
|
|
60
|
+
raise if ENV['DEBUG']
|
|
61
|
+
|
|
62
|
+
exit 1
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
desc 'show NAME', 'Display full persona details'
|
|
66
|
+
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
67
|
+
def show(name)
|
|
68
|
+
ctx = Helpers::ClusterContext.from_options(options)
|
|
69
|
+
|
|
70
|
+
begin
|
|
71
|
+
persona = ctx.client.get_resource('LanguagePersona', name, ctx.namespace)
|
|
72
|
+
rescue K8s::Error::NotFound
|
|
73
|
+
Formatters::ProgressFormatter.error("Persona '#{name}' not found in cluster '#{ctx.name}'")
|
|
74
|
+
exit 1
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
puts
|
|
78
|
+
puts "Persona: #{pastel.cyan.bold(name)}"
|
|
79
|
+
puts '═' * 80
|
|
80
|
+
puts
|
|
81
|
+
|
|
82
|
+
# Format and display key persona details
|
|
83
|
+
spec = persona['spec'] || {}
|
|
84
|
+
puts "#{pastel.bold('Display Name:')} #{spec['displayName']}"
|
|
85
|
+
puts "#{pastel.bold('Tone:')} #{pastel.yellow(spec['tone'])}" if spec['tone']
|
|
86
|
+
puts
|
|
87
|
+
puts pastel.bold('Description:')
|
|
88
|
+
puts " #{spec['description']}"
|
|
89
|
+
puts
|
|
90
|
+
|
|
91
|
+
if spec['systemPrompt']
|
|
92
|
+
puts pastel.bold('System Prompt:')
|
|
93
|
+
puts " #{spec['systemPrompt']}"
|
|
94
|
+
puts
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
if spec['capabilities']&.any?
|
|
98
|
+
puts pastel.bold('Capabilities:')
|
|
99
|
+
spec['capabilities'].each do |cap|
|
|
100
|
+
puts " #{pastel.green('•')} #{cap}"
|
|
101
|
+
end
|
|
102
|
+
puts
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
if spec['toolPreferences']&.any?
|
|
106
|
+
puts pastel.bold('Tool Preferences:')
|
|
107
|
+
spec['toolPreferences'].each do |pref|
|
|
108
|
+
puts " #{pastel.green('•')} #{pref}"
|
|
109
|
+
end
|
|
110
|
+
puts
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
if spec['responseFormat']
|
|
114
|
+
puts "#{pastel.bold('Response Format:')} #{spec['responseFormat']}"
|
|
115
|
+
puts
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
puts '═' * 80
|
|
119
|
+
puts
|
|
120
|
+
Formatters::ProgressFormatter.info('Use this persona when creating agents:')
|
|
121
|
+
puts " aictl agent create \"description\" --persona #{name}"
|
|
122
|
+
puts
|
|
123
|
+
rescue StandardError => e
|
|
124
|
+
Formatters::ProgressFormatter.error("Failed to show persona: #{e.message}")
|
|
125
|
+
raise if ENV['DEBUG']
|
|
126
|
+
|
|
127
|
+
exit 1
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
desc 'create NAME', 'Create a new persona'
|
|
131
|
+
long_desc <<-DESC
|
|
132
|
+
Create a new persona with the specified name using an interactive wizard.
|
|
133
|
+
|
|
134
|
+
Examples:
|
|
135
|
+
aictl persona create helpful-assistant
|
|
136
|
+
aictl persona create code-reviewer --from helpful-assistant
|
|
137
|
+
DESC
|
|
138
|
+
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
139
|
+
option :from, type: :string, desc: 'Copy from existing persona as starting point'
|
|
140
|
+
def create(name)
|
|
141
|
+
ctx = Helpers::ClusterContext.from_options(options)
|
|
142
|
+
|
|
143
|
+
# Check if persona already exists
|
|
144
|
+
begin
|
|
145
|
+
ctx.client.get_resource('LanguagePersona', name, ctx.namespace)
|
|
146
|
+
Formatters::ProgressFormatter.error("Persona '#{name}' already exists in cluster '#{ctx.name}'")
|
|
147
|
+
puts
|
|
148
|
+
puts 'Use a different name or delete the existing persona first:'
|
|
149
|
+
puts " aictl persona delete #{name}"
|
|
150
|
+
exit 1
|
|
151
|
+
rescue K8s::Error::NotFound
|
|
152
|
+
# Good - persona doesn't exist yet
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# If --from flag provided, copy from existing persona
|
|
156
|
+
base_persona = nil
|
|
157
|
+
if options[:from]
|
|
158
|
+
begin
|
|
159
|
+
base_persona = ctx.client.get_resource('LanguagePersona', options[:from], ctx.namespace)
|
|
160
|
+
Formatters::ProgressFormatter.info("Copying from persona '#{options[:from]}'")
|
|
161
|
+
puts
|
|
162
|
+
rescue K8s::Error::NotFound
|
|
163
|
+
Formatters::ProgressFormatter.error("Source persona '#{options[:from]}' not found")
|
|
164
|
+
exit 1
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Interactive prompts
|
|
169
|
+
require 'tty-prompt'
|
|
170
|
+
prompt = TTY::Prompt.new
|
|
171
|
+
|
|
172
|
+
puts
|
|
173
|
+
puts '=' * 80
|
|
174
|
+
puts ' Create New Persona'
|
|
175
|
+
puts '=' * 80
|
|
176
|
+
puts
|
|
177
|
+
|
|
178
|
+
# Get display name
|
|
179
|
+
default_display_name = base_persona&.dig('spec', 'displayName') || name.split('-').map(&:capitalize).join(' ')
|
|
180
|
+
display_name = prompt.ask('Display Name:', default: default_display_name)
|
|
181
|
+
|
|
182
|
+
# Get description
|
|
183
|
+
default_description = base_persona&.dig('spec', 'description') || ''
|
|
184
|
+
description = prompt.ask('Description:', default: default_description) do |q|
|
|
185
|
+
q.required true
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Get tone
|
|
189
|
+
default_tone = base_persona&.dig('spec', 'tone') || 'neutral'
|
|
190
|
+
tone = prompt.select('Tone:', %w[neutral friendly professional technical creative], default: default_tone)
|
|
191
|
+
|
|
192
|
+
# Get system prompt
|
|
193
|
+
puts
|
|
194
|
+
puts 'System Prompt (press Enter to open editor):'
|
|
195
|
+
prompt.keypress('Press any key to continue...')
|
|
196
|
+
|
|
197
|
+
default_system_prompt = base_persona&.dig('spec', 'systemPrompt') || ''
|
|
198
|
+
system_prompt = edit_in_editor(default_system_prompt, 'persona-system-prompt')
|
|
199
|
+
|
|
200
|
+
if system_prompt.strip.empty?
|
|
201
|
+
Formatters::ProgressFormatter.error('System prompt cannot be empty')
|
|
202
|
+
exit 1
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Get capabilities
|
|
206
|
+
puts
|
|
207
|
+
puts 'Capabilities (optional, press Enter to open editor, or leave empty to skip):'
|
|
208
|
+
puts 'Describe what this persona can do, one per line.'
|
|
209
|
+
prompt.keypress('Press any key to continue...')
|
|
210
|
+
|
|
211
|
+
default_capabilities = base_persona&.dig('spec', 'capabilities')&.join("\n") || ''
|
|
212
|
+
capabilities_text = edit_in_editor(default_capabilities, 'persona-capabilities')
|
|
213
|
+
capabilities = capabilities_text.strip.empty? ? [] : capabilities_text.strip.split("\n").map(&:strip).reject(&:empty?)
|
|
214
|
+
|
|
215
|
+
# Build persona resource
|
|
216
|
+
persona_spec = {
|
|
217
|
+
'displayName' => display_name,
|
|
218
|
+
'description' => description,
|
|
219
|
+
'tone' => tone,
|
|
220
|
+
'systemPrompt' => system_prompt.strip
|
|
221
|
+
}
|
|
222
|
+
persona_spec['capabilities'] = capabilities unless capabilities.empty?
|
|
223
|
+
|
|
224
|
+
persona_resource = Kubernetes::ResourceBuilder.build_persona(
|
|
225
|
+
name: name,
|
|
226
|
+
spec: persona_spec,
|
|
227
|
+
namespace: ctx.namespace
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Show preview
|
|
231
|
+
puts
|
|
232
|
+
puts '=' * 80
|
|
233
|
+
puts 'Preview:'
|
|
234
|
+
puts '=' * 80
|
|
235
|
+
puts YAML.dump(persona_resource)
|
|
236
|
+
puts '=' * 80
|
|
237
|
+
puts
|
|
238
|
+
|
|
239
|
+
# Confirm creation
|
|
240
|
+
unless prompt.yes?('Create this persona?')
|
|
241
|
+
puts 'Creation cancelled'
|
|
242
|
+
return
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Create persona
|
|
246
|
+
Formatters::ProgressFormatter.with_spinner("Creating persona '#{name}'") do
|
|
247
|
+
ctx.client.create_resource(persona_resource)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
Formatters::ProgressFormatter.success("Persona '#{name}' created successfully")
|
|
251
|
+
puts
|
|
252
|
+
puts 'Use this persona when creating agents:'
|
|
253
|
+
puts " aictl agent create \"description\" --persona #{name}"
|
|
254
|
+
rescue StandardError => e
|
|
255
|
+
Formatters::ProgressFormatter.error("Failed to create persona: #{e.message}")
|
|
256
|
+
raise if ENV['DEBUG']
|
|
257
|
+
|
|
258
|
+
exit 1
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
desc 'edit NAME', 'Edit an existing persona'
|
|
262
|
+
long_desc <<-DESC
|
|
263
|
+
Edit an existing persona by opening the YAML definition in your editor.
|
|
264
|
+
|
|
265
|
+
When you save and close the editor, the persona will be updated.
|
|
266
|
+
Any agents using this persona will be automatically re-synthesized.
|
|
267
|
+
|
|
268
|
+
Example:
|
|
269
|
+
aictl persona edit helpful-assistant
|
|
270
|
+
DESC
|
|
271
|
+
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
272
|
+
def edit(name)
|
|
273
|
+
ctx = Helpers::ClusterContext.from_options(options)
|
|
274
|
+
|
|
275
|
+
# Get current persona
|
|
276
|
+
begin
|
|
277
|
+
persona = ctx.client.get_resource('LanguagePersona', name, ctx.namespace)
|
|
278
|
+
rescue K8s::Error::NotFound
|
|
279
|
+
Formatters::ProgressFormatter.error("Persona '#{name}' not found in cluster '#{ctx.name}'")
|
|
280
|
+
exit 1
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Open in editor
|
|
284
|
+
original_yaml = YAML.dump(persona)
|
|
285
|
+
edited_yaml = edit_in_editor(original_yaml, "persona-#{name}")
|
|
286
|
+
|
|
287
|
+
# Check if changed
|
|
288
|
+
if edited_yaml.strip == original_yaml.strip
|
|
289
|
+
Formatters::ProgressFormatter.info('No changes made')
|
|
290
|
+
return
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Parse edited YAML
|
|
294
|
+
begin
|
|
295
|
+
edited_persona = YAML.safe_load(edited_yaml)
|
|
296
|
+
rescue Psych::SyntaxError => e
|
|
297
|
+
Formatters::ProgressFormatter.error("Invalid YAML: #{e.message}")
|
|
298
|
+
exit 1
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Validate structure
|
|
302
|
+
unless edited_persona.is_a?(Hash) && edited_persona['spec']
|
|
303
|
+
Formatters::ProgressFormatter.error('Invalid persona structure: missing spec')
|
|
304
|
+
exit 1
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Update persona
|
|
308
|
+
Formatters::ProgressFormatter.with_spinner("Updating persona '#{name}'") do
|
|
309
|
+
ctx.client.update_resource(edited_persona)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
Formatters::ProgressFormatter.success("Persona '#{name}' updated successfully")
|
|
313
|
+
|
|
314
|
+
# Check for agents using this persona
|
|
315
|
+
agents = ctx.client.list_resources('LanguageAgent', namespace: ctx.namespace)
|
|
316
|
+
agents_using = Helpers::ResourceDependencyChecker.agents_using_persona(agents, name)
|
|
317
|
+
|
|
318
|
+
if agents_using.any?
|
|
319
|
+
puts
|
|
320
|
+
Formatters::ProgressFormatter.info("#{agents_using.count} agent(s) will be re-synthesized automatically:")
|
|
321
|
+
agents_using.each do |agent|
|
|
322
|
+
puts " - #{agent.dig('metadata', 'name')}"
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
rescue StandardError => e
|
|
326
|
+
Formatters::ProgressFormatter.error("Failed to edit persona: #{e.message}")
|
|
327
|
+
raise if ENV['DEBUG']
|
|
328
|
+
|
|
329
|
+
exit 1
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
desc 'delete NAME', 'Delete a persona'
|
|
333
|
+
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
334
|
+
option :force, type: :boolean, default: false, desc: 'Skip confirmation'
|
|
335
|
+
def delete(name)
|
|
336
|
+
ctx = Helpers::ClusterContext.from_options(options)
|
|
337
|
+
|
|
338
|
+
# Get persona
|
|
339
|
+
begin
|
|
340
|
+
persona = ctx.client.get_resource('LanguagePersona', name, ctx.namespace)
|
|
341
|
+
rescue K8s::Error::NotFound
|
|
342
|
+
Formatters::ProgressFormatter.error("Persona '#{name}' not found in cluster '#{ctx.name}'")
|
|
343
|
+
exit 1
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Check for agents using this persona
|
|
347
|
+
agents = ctx.client.list_resources('LanguageAgent', namespace: ctx.namespace)
|
|
348
|
+
agents_using = Helpers::ResourceDependencyChecker.agents_using_persona(agents, name)
|
|
349
|
+
|
|
350
|
+
if agents_using.any? && !options[:force]
|
|
351
|
+
Formatters::ProgressFormatter.warn("Persona '#{name}' is in use by #{agents_using.count} agent(s)")
|
|
352
|
+
puts
|
|
353
|
+
puts 'Agents using this persona:'
|
|
354
|
+
agents_using.each do |agent|
|
|
355
|
+
puts " - #{agent.dig('metadata', 'name')}"
|
|
356
|
+
end
|
|
357
|
+
puts
|
|
358
|
+
puts 'Delete these agents first, or use --force to delete anyway.'
|
|
359
|
+
puts
|
|
360
|
+
return unless Helpers::UserPrompts.confirm('Are you sure?')
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Confirm deletion unless --force
|
|
364
|
+
unless options[:force] || agents_using.any?
|
|
365
|
+
puts "This will delete persona '#{name}' from cluster '#{ctx.name}':"
|
|
366
|
+
puts " Tone: #{persona.dig('spec', 'tone')}"
|
|
367
|
+
puts " Description: #{persona.dig('spec', 'description')}"
|
|
368
|
+
puts
|
|
369
|
+
return unless Helpers::UserPrompts.confirm('Are you sure?')
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Delete persona
|
|
373
|
+
Formatters::ProgressFormatter.with_spinner("Deleting persona '#{name}'") do
|
|
374
|
+
ctx.client.delete_resource('LanguagePersona', name, ctx.namespace)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
Formatters::ProgressFormatter.success("Persona '#{name}' deleted successfully")
|
|
378
|
+
rescue StandardError => e
|
|
379
|
+
Formatters::ProgressFormatter.error("Failed to delete persona: #{e.message}")
|
|
380
|
+
raise if ENV['DEBUG']
|
|
381
|
+
|
|
382
|
+
exit 1
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
private
|
|
386
|
+
|
|
387
|
+
def edit_in_editor(content, filename_prefix)
|
|
388
|
+
Helpers::EditorHelper.edit_content(content, filename_prefix, '.txt')
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
require_relative '../formatters/progress_formatter'
|
|
5
|
+
require_relative '../wizards/quickstart_wizard'
|
|
6
|
+
|
|
7
|
+
module LanguageOperator
|
|
8
|
+
module CLI
|
|
9
|
+
module Commands
|
|
10
|
+
# Quickstart wizard for first-time users
|
|
11
|
+
class Quickstart < Thor
|
|
12
|
+
desc 'start', 'Interactive setup wizard for first-time users'
|
|
13
|
+
def start
|
|
14
|
+
wizard = Wizards::QuickstartWizard.new
|
|
15
|
+
wizard.run
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
default_task :start
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
require_relative '../formatters/progress_formatter'
|
|
5
|
+
require_relative '../formatters/table_formatter'
|
|
6
|
+
require_relative '../formatters/status_formatter'
|
|
7
|
+
require_relative '../helpers/pastel_helper'
|
|
8
|
+
require_relative '../../config/cluster_config'
|
|
9
|
+
require_relative '../../kubernetes/client'
|
|
10
|
+
require_relative '../helpers/cluster_validator'
|
|
11
|
+
|
|
12
|
+
module LanguageOperator
|
|
13
|
+
module CLI
|
|
14
|
+
module Commands
|
|
15
|
+
# System status and overview command
|
|
16
|
+
class Status < Thor
|
|
17
|
+
include Helpers::PastelHelper
|
|
18
|
+
|
|
19
|
+
desc 'overview', 'Show system status and overview'
|
|
20
|
+
def overview
|
|
21
|
+
current_cluster = Config::ClusterConfig.current_cluster
|
|
22
|
+
clusters = Config::ClusterConfig.list_clusters
|
|
23
|
+
|
|
24
|
+
# Current cluster context
|
|
25
|
+
if current_cluster
|
|
26
|
+
cluster_config = Config::ClusterConfig.get_cluster(current_cluster)
|
|
27
|
+
|
|
28
|
+
puts "\nCluster Details"
|
|
29
|
+
puts '----------------'
|
|
30
|
+
puts "Name: #{pastel.bold.white(current_cluster)}"
|
|
31
|
+
puts "Namespace: #{pastel.bold.white(cluster_config[:namespace])}"
|
|
32
|
+
puts
|
|
33
|
+
|
|
34
|
+
# Check cluster health
|
|
35
|
+
begin
|
|
36
|
+
k8s = Helpers::ClusterValidator.kubernetes_client(current_cluster)
|
|
37
|
+
|
|
38
|
+
# Operator status
|
|
39
|
+
if k8s.operator_installed?
|
|
40
|
+
version = k8s.operator_version || 'installed'
|
|
41
|
+
Formatters::ProgressFormatter.success("Operator: #{version}")
|
|
42
|
+
else
|
|
43
|
+
Formatters::ProgressFormatter.error('Operator: not installed')
|
|
44
|
+
puts
|
|
45
|
+
puts 'Install the operator with:'
|
|
46
|
+
puts ' aictl install'
|
|
47
|
+
puts
|
|
48
|
+
return
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Agent statistics
|
|
52
|
+
agents = k8s.list_resources('LanguageAgent', namespace: cluster_config[:namespace])
|
|
53
|
+
agent_stats = categorize_by_status(agents)
|
|
54
|
+
|
|
55
|
+
puts
|
|
56
|
+
puts "Agents (#{agents.count} total):"
|
|
57
|
+
agent_stats.each do |status, count|
|
|
58
|
+
puts " #{format_status(status)}: #{count}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Tool statistics
|
|
62
|
+
tools = k8s.list_resources('LanguageTool', namespace: cluster_config[:namespace])
|
|
63
|
+
puts
|
|
64
|
+
puts "Tools (#{tools.count} total):"
|
|
65
|
+
if tools.any?
|
|
66
|
+
tool_types = tools.group_by { |t| t.dig('spec', 'type') }
|
|
67
|
+
tool_types.each do |type, items|
|
|
68
|
+
puts " #{type}: #{items.count}"
|
|
69
|
+
end
|
|
70
|
+
else
|
|
71
|
+
puts ' (none)'
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Model statistics
|
|
75
|
+
models = k8s.list_resources('LanguageModel', namespace: cluster_config[:namespace])
|
|
76
|
+
puts
|
|
77
|
+
puts "Models (#{models.count} total):"
|
|
78
|
+
if models.any?
|
|
79
|
+
model_providers = models.group_by { |m| m.dig('spec', 'provider') }
|
|
80
|
+
model_providers.each do |provider, items|
|
|
81
|
+
puts " #{provider}: #{items.count}"
|
|
82
|
+
end
|
|
83
|
+
else
|
|
84
|
+
puts ' (none)'
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Persona statistics
|
|
88
|
+
personas = k8s.list_resources('LanguagePersona', namespace: cluster_config[:namespace])
|
|
89
|
+
puts
|
|
90
|
+
puts "Personas (#{personas.count} total):"
|
|
91
|
+
if personas.any?
|
|
92
|
+
personas.each do |persona|
|
|
93
|
+
tone = persona.dig('spec', 'tone')
|
|
94
|
+
puts " - #{persona.dig('metadata', 'name')} (#{tone})"
|
|
95
|
+
end
|
|
96
|
+
else
|
|
97
|
+
puts ' (none)'
|
|
98
|
+
end
|
|
99
|
+
rescue StandardError => e
|
|
100
|
+
Formatters::ProgressFormatter.error("Connection failed: #{e.message}")
|
|
101
|
+
puts
|
|
102
|
+
puts 'Check your cluster connection and try again'
|
|
103
|
+
end
|
|
104
|
+
else
|
|
105
|
+
Formatters::ProgressFormatter.warn('No cluster selected')
|
|
106
|
+
puts
|
|
107
|
+
if clusters.any?
|
|
108
|
+
puts 'Available clusters:'
|
|
109
|
+
clusters.each do |cluster|
|
|
110
|
+
puts " - #{cluster[:name]}"
|
|
111
|
+
end
|
|
112
|
+
puts
|
|
113
|
+
puts 'Select a cluster with:'
|
|
114
|
+
puts ' aictl use <cluster>'
|
|
115
|
+
else
|
|
116
|
+
puts 'No clusters found. Create one with:'
|
|
117
|
+
puts ' aictl cluster create <name>'
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
puts
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
default_task :overview
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def format_status(status)
|
|
129
|
+
Formatters::StatusFormatter.format(status)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def categorize_by_status(resources)
|
|
133
|
+
stats = Hash.new(0)
|
|
134
|
+
resources.each do |resource|
|
|
135
|
+
status = resource.dig('status', 'phase') || 'Unknown'
|
|
136
|
+
stats[status] += 1
|
|
137
|
+
end
|
|
138
|
+
stats
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|