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.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +125 -0
  3. data/CHANGELOG.md +88 -0
  4. data/Gemfile +8 -0
  5. data/Gemfile.lock +284 -0
  6. data/LICENSE +229 -21
  7. data/Makefile +82 -0
  8. data/README.md +3 -11
  9. data/Rakefile +63 -0
  10. data/bin/aictl +7 -0
  11. data/completions/_aictl +232 -0
  12. data/completions/aictl.bash +121 -0
  13. data/completions/aictl.fish +114 -0
  14. data/docs/architecture/agent-runtime.md +585 -0
  15. data/docs/dsl/SCHEMA_VERSION.md +250 -0
  16. data/docs/dsl/agent-reference.md +604 -0
  17. data/docs/dsl/best-practices.md +1078 -0
  18. data/docs/dsl/chat-endpoints.md +895 -0
  19. data/docs/dsl/constraints.md +671 -0
  20. data/docs/dsl/mcp-integration.md +1177 -0
  21. data/docs/dsl/webhooks.md +932 -0
  22. data/docs/dsl/workflows.md +744 -0
  23. data/lib/language_operator/agent/base.rb +110 -0
  24. data/lib/language_operator/agent/executor.rb +440 -0
  25. data/lib/language_operator/agent/instrumentation.rb +54 -0
  26. data/lib/language_operator/agent/metrics_tracker.rb +183 -0
  27. data/lib/language_operator/agent/safety/ast_validator.rb +272 -0
  28. data/lib/language_operator/agent/safety/audit_logger.rb +104 -0
  29. data/lib/language_operator/agent/safety/budget_tracker.rb +175 -0
  30. data/lib/language_operator/agent/safety/content_filter.rb +93 -0
  31. data/lib/language_operator/agent/safety/manager.rb +207 -0
  32. data/lib/language_operator/agent/safety/rate_limiter.rb +150 -0
  33. data/lib/language_operator/agent/safety/safe_executor.rb +127 -0
  34. data/lib/language_operator/agent/scheduler.rb +183 -0
  35. data/lib/language_operator/agent/telemetry.rb +116 -0
  36. data/lib/language_operator/agent/web_server.rb +610 -0
  37. data/lib/language_operator/agent/webhook_authenticator.rb +226 -0
  38. data/lib/language_operator/agent.rb +149 -0
  39. data/lib/language_operator/cli/commands/agent.rb +1205 -0
  40. data/lib/language_operator/cli/commands/cluster.rb +371 -0
  41. data/lib/language_operator/cli/commands/install.rb +404 -0
  42. data/lib/language_operator/cli/commands/model.rb +266 -0
  43. data/lib/language_operator/cli/commands/persona.rb +393 -0
  44. data/lib/language_operator/cli/commands/quickstart.rb +22 -0
  45. data/lib/language_operator/cli/commands/status.rb +143 -0
  46. data/lib/language_operator/cli/commands/system.rb +772 -0
  47. data/lib/language_operator/cli/commands/tool.rb +537 -0
  48. data/lib/language_operator/cli/commands/use.rb +47 -0
  49. data/lib/language_operator/cli/errors/handler.rb +180 -0
  50. data/lib/language_operator/cli/errors/suggestions.rb +176 -0
  51. data/lib/language_operator/cli/formatters/code_formatter.rb +77 -0
  52. data/lib/language_operator/cli/formatters/log_formatter.rb +288 -0
  53. data/lib/language_operator/cli/formatters/progress_formatter.rb +49 -0
  54. data/lib/language_operator/cli/formatters/status_formatter.rb +37 -0
  55. data/lib/language_operator/cli/formatters/table_formatter.rb +163 -0
  56. data/lib/language_operator/cli/formatters/value_formatter.rb +113 -0
  57. data/lib/language_operator/cli/helpers/cluster_context.rb +62 -0
  58. data/lib/language_operator/cli/helpers/cluster_validator.rb +101 -0
  59. data/lib/language_operator/cli/helpers/editor_helper.rb +58 -0
  60. data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +167 -0
  61. data/lib/language_operator/cli/helpers/pastel_helper.rb +24 -0
  62. data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +74 -0
  63. data/lib/language_operator/cli/helpers/schedule_builder.rb +108 -0
  64. data/lib/language_operator/cli/helpers/user_prompts.rb +69 -0
  65. data/lib/language_operator/cli/main.rb +236 -0
  66. data/lib/language_operator/cli/templates/tools/generic.yaml +66 -0
  67. data/lib/language_operator/cli/wizards/agent_wizard.rb +246 -0
  68. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +588 -0
  69. data/lib/language_operator/client/base.rb +214 -0
  70. data/lib/language_operator/client/config.rb +136 -0
  71. data/lib/language_operator/client/cost_calculator.rb +37 -0
  72. data/lib/language_operator/client/mcp_connector.rb +123 -0
  73. data/lib/language_operator/client.rb +19 -0
  74. data/lib/language_operator/config/cluster_config.rb +101 -0
  75. data/lib/language_operator/config/tool_patterns.yaml +57 -0
  76. data/lib/language_operator/config/tool_registry.rb +96 -0
  77. data/lib/language_operator/config.rb +138 -0
  78. data/lib/language_operator/dsl/adapter.rb +124 -0
  79. data/lib/language_operator/dsl/agent_context.rb +90 -0
  80. data/lib/language_operator/dsl/agent_definition.rb +427 -0
  81. data/lib/language_operator/dsl/chat_endpoint_definition.rb +115 -0
  82. data/lib/language_operator/dsl/config.rb +119 -0
  83. data/lib/language_operator/dsl/context.rb +50 -0
  84. data/lib/language_operator/dsl/execution_context.rb +47 -0
  85. data/lib/language_operator/dsl/helpers.rb +109 -0
  86. data/lib/language_operator/dsl/http.rb +184 -0
  87. data/lib/language_operator/dsl/mcp_server_definition.rb +73 -0
  88. data/lib/language_operator/dsl/parameter_definition.rb +124 -0
  89. data/lib/language_operator/dsl/registry.rb +36 -0
  90. data/lib/language_operator/dsl/schema.rb +1102 -0
  91. data/lib/language_operator/dsl/shell.rb +125 -0
  92. data/lib/language_operator/dsl/tool_definition.rb +112 -0
  93. data/lib/language_operator/dsl/webhook_authentication.rb +114 -0
  94. data/lib/language_operator/dsl/webhook_definition.rb +106 -0
  95. data/lib/language_operator/dsl/workflow_definition.rb +259 -0
  96. data/lib/language_operator/dsl.rb +161 -0
  97. data/lib/language_operator/errors.rb +60 -0
  98. data/lib/language_operator/kubernetes/client.rb +279 -0
  99. data/lib/language_operator/kubernetes/resource_builder.rb +194 -0
  100. data/lib/language_operator/loggable.rb +47 -0
  101. data/lib/language_operator/logger.rb +141 -0
  102. data/lib/language_operator/retry.rb +123 -0
  103. data/lib/language_operator/retryable.rb +132 -0
  104. data/lib/language_operator/templates/README.md +23 -0
  105. data/lib/language_operator/templates/examples/agent_synthesis.tmpl +115 -0
  106. data/lib/language_operator/templates/examples/persona_distillation.tmpl +19 -0
  107. data/lib/language_operator/templates/schema/.gitkeep +0 -0
  108. data/lib/language_operator/templates/schema/CHANGELOG.md +93 -0
  109. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +306 -0
  110. data/lib/language_operator/templates/schema/agent_dsl_schema.json +452 -0
  111. data/lib/language_operator/tool_loader.rb +242 -0
  112. data/lib/language_operator/validators.rb +170 -0
  113. data/lib/language_operator/version.rb +1 -1
  114. data/lib/language_operator.rb +65 -3
  115. data/requirements/tasks/challenge.md +9 -0
  116. data/requirements/tasks/iterate.md +36 -0
  117. data/requirements/tasks/optimize.md +21 -0
  118. data/requirements/tasks/tag.md +5 -0
  119. data/test_agent_dsl.rb +108 -0
  120. 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