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,404 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'open3'
5
+ require_relative '../formatters/progress_formatter'
6
+ require_relative '../helpers/cluster_validator'
7
+ require_relative '../helpers/user_prompts'
8
+
9
+ module LanguageOperator
10
+ module CLI
11
+ module Commands
12
+ # Install, upgrade, and uninstall commands for the language-operator
13
+ class Install < Thor
14
+ HELM_REPO_NAME = 'language-operator'
15
+ HELM_REPO_URL = 'https://language-operator.github.io/charts'
16
+ CHART_NAME = 'language-operator/language-operator'
17
+ RELEASE_NAME = 'language-operator'
18
+ DEFAULT_NAMESPACE = 'language-operator-system'
19
+
20
+ # Long descriptions for commands
21
+ LONG_DESCRIPTIONS = {
22
+ install: <<-DESC,
23
+ Install the language-operator into your Kubernetes cluster using Helm.
24
+
25
+ This command will:
26
+ 1. Add the language-operator Helm repository
27
+ 2. Update Helm repositories
28
+ 3. Install the language-operator chart
29
+ 4. Verify the installation
30
+
31
+ Examples:
32
+ # Install with defaults
33
+ aictl install
34
+
35
+ # Install with custom values
36
+ aictl install --values my-values.yaml
37
+
38
+ # Install specific version
39
+ aictl install --version 0.1.0
40
+
41
+ # Dry run to see what would be installed
42
+ aictl install --dry-run
43
+ DESC
44
+ upgrade: <<-DESC,
45
+ Upgrade the language-operator to a newer version using Helm.
46
+
47
+ This command will:
48
+ 1. Update Helm repositories
49
+ 2. Upgrade the language-operator release
50
+ 3. Wait for the rollout to complete
51
+ 4. Verify the operator is running
52
+
53
+ Examples:
54
+ # Upgrade to latest version
55
+ aictl upgrade
56
+
57
+ # Upgrade with custom values
58
+ aictl upgrade --values my-values.yaml
59
+
60
+ # Upgrade to specific version
61
+ aictl upgrade --version 0.2.0
62
+ DESC
63
+ uninstall: <<-DESC
64
+ Uninstall the language-operator from your Kubernetes cluster.
65
+
66
+ WARNING: This will remove the operator but NOT the CRDs or custom resources.
67
+ Agents, tools, models, and personas will remain in the cluster.
68
+
69
+ Examples:
70
+ # Uninstall with confirmation
71
+ aictl uninstall
72
+
73
+ # Force uninstall without confirmation
74
+ aictl uninstall --force
75
+
76
+ # Uninstall from specific namespace
77
+ aictl uninstall --namespace my-namespace
78
+ DESC
79
+ }.freeze
80
+
81
+ # Helper method to get long descriptions for commands
82
+ def self.long_desc_for(command)
83
+ LONG_DESCRIPTIONS[command]
84
+ end
85
+
86
+ desc 'install', 'Install the language-operator using Helm'
87
+ long_desc <<-DESC
88
+ Install the language-operator into your Kubernetes cluster using Helm.
89
+
90
+ This command will:
91
+ 1. Add the language-operator Helm repository
92
+ 2. Update Helm repositories
93
+ 3. Install the language-operator chart
94
+ 4. Verify the installation
95
+
96
+ Examples:
97
+ # Install with defaults
98
+ aictl install
99
+
100
+ # Install with custom values
101
+ aictl install --values my-values.yaml
102
+
103
+ # Install specific version
104
+ aictl install --version 0.1.0
105
+
106
+ # Dry run to see what would be installed
107
+ aictl install --dry-run
108
+ DESC
109
+ option :values, type: :string, desc: 'Path to custom Helm values file'
110
+ option :namespace, type: :string, default: DEFAULT_NAMESPACE, desc: 'Kubernetes namespace'
111
+ option :version, type: :string, desc: 'Specific chart version to install'
112
+ option :dry_run, type: :boolean, default: false, desc: 'Preview installation without applying'
113
+ option :wait, type: :boolean, default: true, desc: 'Wait for deployment to complete'
114
+ option :create_namespace, type: :boolean, default: true, desc: 'Create namespace if it does not exist'
115
+ def install
116
+ # Check if helm is available
117
+ check_helm_installed!
118
+
119
+ # Check if operator is already installed
120
+ if operator_installed? && !options[:dry_run]
121
+ Formatters::ProgressFormatter.warn('Language operator is already installed')
122
+ puts
123
+ puts 'To upgrade, use:'
124
+ puts ' aictl upgrade'
125
+ return
126
+ end
127
+
128
+ namespace = options[:namespace]
129
+
130
+ puts 'Installing language-operator...'
131
+ puts " Namespace: #{namespace}"
132
+ puts " Chart: #{CHART_NAME}"
133
+ puts
134
+
135
+ # Add Helm repository
136
+ add_helm_repo unless options[:dry_run]
137
+
138
+ # Build helm install command
139
+ cmd = build_helm_command('install', namespace)
140
+
141
+ # Execute helm install
142
+ if options[:dry_run]
143
+ puts 'Dry run - would execute:'
144
+ puts " #{cmd}"
145
+ puts
146
+ success, output = run_helm_command(cmd)
147
+ puts output
148
+ else
149
+ Formatters::ProgressFormatter.with_spinner('Installing language-operator') do
150
+ success, output = run_helm_command(cmd)
151
+ raise "Helm install failed: #{output}" unless success
152
+ end
153
+
154
+ Formatters::ProgressFormatter.success('Language operator installed successfully!')
155
+ puts
156
+ puts 'Next steps:'
157
+ puts ' 1. Create a cluster: aictl cluster create my-cluster'
158
+ puts ' 2. Create a model: aictl model create gpt4 --provider openai --model gpt-4-turbo'
159
+ puts ' 3. Create an agent: aictl agent create "your agent description"'
160
+ end
161
+ rescue StandardError => e
162
+ Formatters::ProgressFormatter.error("Installation failed: #{e.message}")
163
+ raise if ENV['DEBUG']
164
+
165
+ exit 1
166
+ end
167
+
168
+ desc 'upgrade', 'Upgrade the language-operator using Helm'
169
+ long_desc <<-DESC
170
+ Upgrade the language-operator to a newer version using Helm.
171
+
172
+ This command will:
173
+ 1. Update Helm repositories
174
+ 2. Upgrade the language-operator release
175
+ 3. Wait for the rollout to complete
176
+ 4. Verify the operator is running
177
+
178
+ Examples:
179
+ # Upgrade to latest version
180
+ aictl upgrade
181
+
182
+ # Upgrade with custom values
183
+ aictl upgrade --values my-values.yaml
184
+
185
+ # Upgrade to specific version
186
+ aictl upgrade --version 0.2.0
187
+ DESC
188
+ option :values, type: :string, desc: 'Path to custom Helm values file'
189
+ option :namespace, type: :string, default: DEFAULT_NAMESPACE, desc: 'Kubernetes namespace'
190
+ option :version, type: :string, desc: 'Specific chart version to upgrade to'
191
+ option :dry_run, type: :boolean, default: false, desc: 'Preview upgrade without applying'
192
+ option :wait, type: :boolean, default: true, desc: 'Wait for deployment to complete'
193
+ def upgrade
194
+ # Check if helm is available
195
+ check_helm_installed!
196
+
197
+ # Check if operator is installed
198
+ unless operator_installed?
199
+ Formatters::ProgressFormatter.error('Language operator is not installed')
200
+ puts
201
+ puts 'To install, use:'
202
+ puts ' aictl install'
203
+ exit 1
204
+ end
205
+
206
+ namespace = options[:namespace]
207
+
208
+ puts 'Upgrading language-operator...'
209
+ puts " Namespace: #{namespace}"
210
+ puts " Chart: #{CHART_NAME}"
211
+ puts
212
+
213
+ # Update Helm repository
214
+ update_helm_repo unless options[:dry_run]
215
+
216
+ # Build helm upgrade command
217
+ cmd = build_helm_command('upgrade', namespace)
218
+
219
+ # Execute helm upgrade
220
+ if options[:dry_run]
221
+ puts 'Dry run - would execute:'
222
+ puts " #{cmd}"
223
+ puts
224
+ success, output = run_helm_command(cmd)
225
+ puts output
226
+ else
227
+ Formatters::ProgressFormatter.with_spinner('Upgrading language-operator') do
228
+ success, output = run_helm_command(cmd)
229
+ raise "Helm upgrade failed: #{output}" unless success
230
+ end
231
+
232
+ Formatters::ProgressFormatter.success('Language operator upgraded successfully!')
233
+ end
234
+ rescue StandardError => e
235
+ Formatters::ProgressFormatter.error("Upgrade failed: #{e.message}")
236
+ raise if ENV['DEBUG']
237
+
238
+ exit 1
239
+ end
240
+
241
+ desc 'uninstall', 'Uninstall the language-operator using Helm'
242
+ long_desc <<-DESC
243
+ Uninstall the language-operator from your Kubernetes cluster.
244
+
245
+ WARNING: This will remove the operator but NOT the CRDs or custom resources.
246
+ Agents, tools, models, and personas will remain in the cluster.
247
+
248
+ Examples:
249
+ # Uninstall with confirmation
250
+ aictl uninstall
251
+
252
+ # Force uninstall without confirmation
253
+ aictl uninstall --force
254
+
255
+ # Uninstall from specific namespace
256
+ aictl uninstall --namespace my-namespace
257
+ DESC
258
+ option :namespace, type: :string, default: DEFAULT_NAMESPACE, desc: 'Kubernetes namespace'
259
+ option :force, type: :boolean, default: false, desc: 'Skip confirmation prompt'
260
+ def uninstall
261
+ # Check if helm is available
262
+ check_helm_installed!
263
+
264
+ # Check if operator is installed
265
+ unless operator_installed?
266
+ Formatters::ProgressFormatter.warn('Language operator is not installed')
267
+ return
268
+ end
269
+
270
+ namespace = options[:namespace]
271
+
272
+ # Confirm deletion unless --force
273
+ unless options[:force]
274
+ puts "This will uninstall the language-operator from namespace '#{namespace}'"
275
+ puts
276
+ puts 'WARNING: This will NOT delete:'
277
+ puts ' - CRDs (CustomResourceDefinitions)'
278
+ puts ' - LanguageAgent resources'
279
+ puts ' - LanguageTool resources'
280
+ puts ' - LanguageModel resources'
281
+ puts ' - LanguagePersona resources'
282
+ puts
283
+ return unless Helpers::UserPrompts.confirm('Continue with uninstall?')
284
+ end
285
+
286
+ # Build helm uninstall command
287
+ cmd = "helm uninstall #{RELEASE_NAME} --namespace #{namespace}"
288
+
289
+ # Execute helm uninstall
290
+ Formatters::ProgressFormatter.with_spinner('Uninstalling language-operator') do
291
+ success, output = run_helm_command(cmd)
292
+ raise "Helm uninstall failed: #{output}" unless success
293
+ end
294
+
295
+ Formatters::ProgressFormatter.success('Language operator uninstalled successfully!')
296
+ puts
297
+ puts 'Note: CRDs and custom resources remain in the cluster.'
298
+ puts 'To completely remove all resources, you must manually delete them.'
299
+ rescue StandardError => e
300
+ Formatters::ProgressFormatter.error("Uninstall failed: #{e.message}")
301
+ raise if ENV['DEBUG']
302
+
303
+ exit 1
304
+ end
305
+
306
+ private
307
+
308
+ def check_helm_installed!
309
+ _, _, status = Open3.capture3('which helm')
310
+ return if status.success?
311
+
312
+ Formatters::ProgressFormatter.error('Helm is not installed')
313
+ puts
314
+ puts 'Install Helm from: https://helm.sh/docs/intro/install/'
315
+ exit 1
316
+ end
317
+
318
+ def operator_installed?
319
+ namespace = options[:namespace] || DEFAULT_NAMESPACE
320
+ cmd = "helm list --namespace #{namespace} --filter #{RELEASE_NAME} --output json"
321
+ success, output = run_helm_command(cmd)
322
+ return false unless success
323
+
324
+ require 'json'
325
+ releases = JSON.parse(output)
326
+ releases.any? { |r| r['name'] == RELEASE_NAME }
327
+ rescue StandardError
328
+ false
329
+ end
330
+
331
+ def add_helm_repo
332
+ Formatters::ProgressFormatter.with_spinner('Adding Helm repository') do
333
+ # Check if repo already exists
334
+ cmd = 'helm repo list --output json'
335
+ success, output = run_helm_command(cmd)
336
+
337
+ if success
338
+ require 'json'
339
+ repos = JSON.parse(output)
340
+ repo_exists = repos.any? { |r| r['name'] == HELM_REPO_NAME }
341
+
342
+ unless repo_exists
343
+ cmd = "helm repo add #{HELM_REPO_NAME} #{HELM_REPO_URL}"
344
+ success, output = run_helm_command(cmd)
345
+ raise "Failed to add Helm repo: #{output}" unless success
346
+ end
347
+ else
348
+ # helm repo list failed, try adding anyway
349
+ cmd = "helm repo add #{HELM_REPO_NAME} #{HELM_REPO_URL}"
350
+ success, output = run_helm_command(cmd)
351
+ raise "Failed to add Helm repo: #{output}" unless success
352
+ end
353
+
354
+ # Update repo
355
+ cmd = "helm repo update #{HELM_REPO_NAME}"
356
+ success, output = run_helm_command(cmd)
357
+ raise "Failed to update Helm repo: #{output}" unless success
358
+ end
359
+ end
360
+
361
+ def update_helm_repo
362
+ Formatters::ProgressFormatter.with_spinner('Updating Helm repository') do
363
+ cmd = "helm repo update #{HELM_REPO_NAME}"
364
+ success, output = run_helm_command(cmd)
365
+ raise "Failed to update Helm repo: #{output}" unless success
366
+ end
367
+ end
368
+
369
+ def build_helm_command(action, namespace)
370
+ cmd = ['helm', action, RELEASE_NAME]
371
+
372
+ # Add chart name for install
373
+ cmd << CHART_NAME if action == 'install'
374
+
375
+ # Add namespace
376
+ cmd << '--namespace' << namespace
377
+
378
+ # Add create-namespace for install
379
+ cmd << '--create-namespace' if action == 'install' && options[:create_namespace]
380
+
381
+ # Add values file
382
+ cmd << '--values' << options[:values] if options[:values]
383
+
384
+ # Add version
385
+ cmd << '--version' << options[:version] if options[:version]
386
+
387
+ # Add wait
388
+ cmd << '--wait' if options[:wait]
389
+
390
+ # Add dry-run
391
+ cmd << '--dry-run' if options[:dry_run]
392
+
393
+ cmd.join(' ')
394
+ end
395
+
396
+ def run_helm_command(cmd)
397
+ stdout, stderr, status = Open3.capture3(cmd)
398
+ output = stdout + stderr
399
+ [status.success?, output.strip]
400
+ end
401
+ end
402
+ end
403
+ end
404
+ end
@@ -0,0 +1,266 @@
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 '../../config/cluster_config'
13
+ require_relative '../../kubernetes/client'
14
+ require_relative '../../kubernetes/resource_builder'
15
+
16
+ module LanguageOperator
17
+ module CLI
18
+ module Commands
19
+ # Model management commands
20
+ class Model < Thor
21
+ include Helpers::ClusterValidator
22
+
23
+ desc 'list', 'List all models in current cluster'
24
+ option :cluster, type: :string, desc: 'Override current cluster context'
25
+ def list
26
+ ctx = Helpers::ClusterContext.from_options(options)
27
+
28
+ models = ctx.client.list_resources('LanguageModel', namespace: ctx.namespace)
29
+
30
+ if models.empty?
31
+ Formatters::ProgressFormatter.info("No models found in cluster '#{ctx.name}'")
32
+ puts
33
+ puts 'Models define LLM configurations for agents.'
34
+ puts
35
+ puts 'Create a model with:'
36
+ puts ' aictl model create <name> --provider <provider> --model <model>'
37
+ return
38
+ end
39
+
40
+ table_data = models.map do |model|
41
+ name = model.dig('metadata', 'name')
42
+ provider = model.dig('spec', 'provider') || 'unknown'
43
+ model_name = model.dig('spec', 'modelName') || 'unknown'
44
+ status = model.dig('status', 'phase') || 'Unknown'
45
+
46
+ {
47
+ name: name,
48
+ provider: provider,
49
+ model: model_name,
50
+ status: status
51
+ }
52
+ end
53
+
54
+ Formatters::TableFormatter.models(table_data)
55
+ rescue StandardError => e
56
+ Formatters::ProgressFormatter.error("Failed to list models: #{e.message}")
57
+ raise if ENV['DEBUG']
58
+
59
+ exit 1
60
+ end
61
+
62
+ desc 'create NAME', 'Create a new model'
63
+ long_desc <<-DESC
64
+ Create a new LanguageModel resource in the cluster.
65
+
66
+ Examples:
67
+ aictl model create gpt4 --provider openai --model gpt-4-turbo
68
+ aictl model create claude --provider anthropic --model claude-3-opus-20240229
69
+ aictl model create local --provider openai_compatible --model llama-3 --endpoint http://localhost:8080
70
+ DESC
71
+ option :provider, type: :string, required: true, desc: 'LLM provider (e.g., openai, anthropic, openai_compatible)'
72
+ option :model, type: :string, required: true, desc: 'Model identifier (e.g., gpt-4, claude-3-opus)'
73
+ option :endpoint, type: :string, desc: 'Custom endpoint URL (for openai_compatible or self-hosted)'
74
+ option :cluster, type: :string, desc: 'Override current cluster context'
75
+ option :dry_run, type: :boolean, default: false, desc: 'Output the manifest without creating'
76
+ def create(name)
77
+ ctx = Helpers::ClusterContext.from_options(options)
78
+
79
+ # Build LanguageModel resource
80
+ resource = Kubernetes::ResourceBuilder.language_model(
81
+ name,
82
+ provider: options[:provider],
83
+ model: options[:model],
84
+ endpoint: options[:endpoint],
85
+ cluster: ctx.namespace
86
+ )
87
+
88
+ # Handle dry-run: output manifest and exit
89
+ if options[:dry_run]
90
+ puts resource.to_yaml
91
+ return
92
+ end
93
+
94
+ # Check if model already exists
95
+ begin
96
+ ctx.client.get_resource('LanguageModel', name, ctx.namespace)
97
+ Formatters::ProgressFormatter.error("Model '#{name}' already exists in cluster '#{ctx.name}'")
98
+ exit 1
99
+ rescue K8s::Error::NotFound
100
+ # Model doesn't exist, proceed with creation
101
+ end
102
+
103
+ # Create model
104
+ Formatters::ProgressFormatter.with_spinner("Creating model '#{name}'") do
105
+ ctx.client.apply_resource(resource)
106
+ end
107
+
108
+ Formatters::ProgressFormatter.success("Model '#{name}' created successfully")
109
+ puts
110
+ puts 'Model Details:'
111
+ puts " Name: #{name}"
112
+ puts " Provider: #{options[:provider]}"
113
+ puts " Model: #{options[:model]}"
114
+ puts " Endpoint: #{options[:endpoint]}" if options[:endpoint]
115
+ puts " Cluster: #{ctx.name}"
116
+ rescue StandardError => e
117
+ Formatters::ProgressFormatter.error("Failed to create model: #{e.message}")
118
+ raise if ENV['DEBUG']
119
+
120
+ exit 1
121
+ end
122
+
123
+ desc 'inspect NAME', 'Show detailed model information'
124
+ option :cluster, type: :string, desc: 'Override current cluster context'
125
+ def inspect(name)
126
+ ctx = Helpers::ClusterContext.from_options(options)
127
+
128
+ # Get model
129
+ begin
130
+ model = ctx.client.get_resource('LanguageModel', name, ctx.namespace)
131
+ rescue K8s::Error::NotFound
132
+ Formatters::ProgressFormatter.error("Model '#{name}' not found in cluster '#{ctx.name}'")
133
+ exit 1
134
+ end
135
+
136
+ puts "Model: #{name}"
137
+ puts " Cluster: #{ctx.name}"
138
+ puts " Namespace: #{ctx.namespace}"
139
+ puts " Provider: #{model.dig('spec', 'provider')}"
140
+ puts " Model: #{model.dig('spec', 'modelName')}"
141
+ puts " Endpoint: #{model.dig('spec', 'endpoint')}" if model.dig('spec', 'endpoint')
142
+ puts " Status: #{model.dig('status', 'phase') || 'Unknown'}"
143
+ puts
144
+
145
+ # Get agents using this model
146
+ agents = ctx.client.list_resources('LanguageAgent', namespace: ctx.namespace)
147
+ agents_using = Helpers::ResourceDependencyChecker.agents_using_model(agents, name)
148
+
149
+ if agents_using.any?
150
+ puts "Agents using this model (#{agents_using.count}):"
151
+ agents_using.each do |agent|
152
+ puts " - #{agent.dig('metadata', 'name')}"
153
+ end
154
+ else
155
+ puts 'No agents using this model'
156
+ end
157
+
158
+ puts
159
+ puts 'Labels:'
160
+ labels = model.dig('metadata', 'labels') || {}
161
+ if labels.empty?
162
+ puts ' (none)'
163
+ else
164
+ labels.each do |key, value|
165
+ puts " #{key}: #{value}"
166
+ end
167
+ end
168
+ rescue StandardError => e
169
+ Formatters::ProgressFormatter.error("Failed to inspect model: #{e.message}")
170
+ raise if ENV['DEBUG']
171
+
172
+ exit 1
173
+ end
174
+
175
+ desc 'delete NAME', 'Delete a model'
176
+ option :cluster, type: :string, desc: 'Override current cluster context'
177
+ option :force, type: :boolean, default: false, desc: 'Skip confirmation'
178
+ def delete(name)
179
+ ctx = Helpers::ClusterContext.from_options(options)
180
+
181
+ # Get model
182
+ begin
183
+ model = ctx.client.get_resource('LanguageModel', name, ctx.namespace)
184
+ rescue K8s::Error::NotFound
185
+ Formatters::ProgressFormatter.error("Model '#{name}' not found in cluster '#{ctx.name}'")
186
+ exit 1
187
+ end
188
+
189
+ # Check for agents using this model
190
+ agents = ctx.client.list_resources('LanguageAgent', namespace: ctx.namespace)
191
+ agents_using = Helpers::ResourceDependencyChecker.agents_using_model(agents, name)
192
+
193
+ if agents_using.any? && !options[:force]
194
+ Formatters::ProgressFormatter.warn("Model '#{name}' is in use by #{agents_using.count} agent(s)")
195
+ puts
196
+ puts 'Agents using this model:'
197
+ agents_using.each do |agent|
198
+ puts " - #{agent.dig('metadata', 'name')}"
199
+ end
200
+ puts
201
+ puts 'Delete these agents first, or use --force to delete anyway.'
202
+ puts
203
+ return unless Helpers::UserPrompts.confirm('Are you sure?')
204
+ end
205
+
206
+ # Confirm deletion unless --force
207
+ unless options[:force] || agents_using.any?
208
+ puts "This will delete model '#{name}' from cluster '#{ctx.name}':"
209
+ puts " Provider: #{model.dig('spec', 'provider')}"
210
+ puts " Model: #{model.dig('spec', 'modelName')}"
211
+ puts " Status: #{model.dig('status', 'phase')}"
212
+ puts
213
+ return unless Helpers::UserPrompts.confirm('Are you sure?')
214
+ end
215
+
216
+ # Delete model
217
+ Formatters::ProgressFormatter.with_spinner("Deleting model '#{name}'") do
218
+ ctx.client.delete_resource('LanguageModel', name, ctx.namespace)
219
+ end
220
+
221
+ Formatters::ProgressFormatter.success("Model '#{name}' deleted successfully")
222
+ rescue StandardError => e
223
+ Formatters::ProgressFormatter.error("Failed to delete model: #{e.message}")
224
+ raise if ENV['DEBUG']
225
+
226
+ exit 1
227
+ end
228
+
229
+ desc 'edit NAME', 'Edit model configuration'
230
+ option :cluster, type: :string, desc: 'Override current cluster context'
231
+ def edit(name)
232
+ ctx = Helpers::ClusterContext.from_options(options)
233
+
234
+ # Get current model
235
+ begin
236
+ model = ctx.client.get_resource('LanguageModel', name, ctx.namespace)
237
+ rescue K8s::Error::NotFound
238
+ Formatters::ProgressFormatter.error("Model '#{name}' not found in cluster '#{ctx.name}'")
239
+ exit 1
240
+ end
241
+
242
+ # Edit model YAML in user's editor
243
+ edited_yaml = Helpers::EditorHelper.edit_content(
244
+ model.to_yaml,
245
+ 'model-',
246
+ '.yaml',
247
+ default_editor: 'vim'
248
+ )
249
+ edited_model = YAML.safe_load(edited_yaml)
250
+
251
+ # Apply changes
252
+ Formatters::ProgressFormatter.with_spinner("Updating model '#{name}'") do
253
+ ctx.client.apply_resource(edited_model)
254
+ end
255
+
256
+ Formatters::ProgressFormatter.success("Model '#{name}' updated successfully")
257
+ rescue StandardError => e
258
+ Formatters::ProgressFormatter.error("Failed to edit model: #{e.message}")
259
+ raise if ENV['DEBUG']
260
+
261
+ exit 1
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end