language-operator 0.1.31 → 0.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -8
  3. data/CHANGELOG.md +14 -0
  4. data/CI_STATUS.md +56 -0
  5. data/Gemfile.lock +2 -2
  6. data/Makefile +22 -6
  7. data/lib/language_operator/agent/base.rb +10 -6
  8. data/lib/language_operator/agent/executor.rb +19 -97
  9. data/lib/language_operator/agent/safety/ast_validator.rb +62 -43
  10. data/lib/language_operator/agent/safety/safe_executor.rb +27 -2
  11. data/lib/language_operator/agent/scheduler.rb +60 -0
  12. data/lib/language_operator/agent/task_executor.rb +548 -0
  13. data/lib/language_operator/agent.rb +90 -27
  14. data/lib/language_operator/cli/base_command.rb +117 -0
  15. data/lib/language_operator/cli/commands/agent.rb +339 -407
  16. data/lib/language_operator/cli/commands/cluster.rb +274 -290
  17. data/lib/language_operator/cli/commands/install.rb +110 -119
  18. data/lib/language_operator/cli/commands/model.rb +284 -184
  19. data/lib/language_operator/cli/commands/persona.rb +218 -284
  20. data/lib/language_operator/cli/commands/quickstart.rb +4 -5
  21. data/lib/language_operator/cli/commands/status.rb +31 -35
  22. data/lib/language_operator/cli/commands/system.rb +221 -233
  23. data/lib/language_operator/cli/commands/tool.rb +356 -422
  24. data/lib/language_operator/cli/commands/use.rb +19 -22
  25. data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +0 -18
  26. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +0 -1
  27. data/lib/language_operator/client/config.rb +20 -21
  28. data/lib/language_operator/config.rb +115 -3
  29. data/lib/language_operator/constants.rb +54 -0
  30. data/lib/language_operator/dsl/agent_context.rb +7 -7
  31. data/lib/language_operator/dsl/agent_definition.rb +111 -26
  32. data/lib/language_operator/dsl/config.rb +30 -66
  33. data/lib/language_operator/dsl/main_definition.rb +114 -0
  34. data/lib/language_operator/dsl/schema.rb +84 -43
  35. data/lib/language_operator/dsl/task_definition.rb +315 -0
  36. data/lib/language_operator/dsl.rb +0 -1
  37. data/lib/language_operator/instrumentation/task_tracer.rb +285 -0
  38. data/lib/language_operator/logger.rb +4 -4
  39. data/lib/language_operator/synthesis_test_harness.rb +324 -0
  40. data/lib/language_operator/templates/examples/agent_synthesis.tmpl +26 -8
  41. data/lib/language_operator/templates/schema/CHANGELOG.md +26 -0
  42. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
  43. data/lib/language_operator/templates/schema/agent_dsl_schema.json +84 -42
  44. data/lib/language_operator/type_coercion.rb +250 -0
  45. data/lib/language_operator/ux/base.rb +81 -0
  46. data/lib/language_operator/ux/concerns/README.md +155 -0
  47. data/lib/language_operator/ux/concerns/headings.rb +90 -0
  48. data/lib/language_operator/ux/concerns/input_validation.rb +146 -0
  49. data/lib/language_operator/ux/concerns/provider_helpers.rb +167 -0
  50. data/lib/language_operator/ux/create_agent.rb +252 -0
  51. data/lib/language_operator/ux/create_model.rb +267 -0
  52. data/lib/language_operator/ux/quickstart.rb +594 -0
  53. data/lib/language_operator/version.rb +1 -1
  54. data/lib/language_operator.rb +2 -0
  55. data/requirements/ARCHITECTURE.md +1 -0
  56. data/requirements/SCRATCH.md +153 -0
  57. data/requirements/dsl.md +0 -0
  58. data/requirements/features +1 -0
  59. data/requirements/personas +1 -0
  60. data/requirements/proposals +1 -0
  61. data/requirements/tasks/iterate.md +14 -15
  62. data/requirements/tasks/optimize.md +13 -4
  63. data/synth/001/Makefile +90 -0
  64. data/synth/001/agent.rb +26 -0
  65. data/synth/001/agent.yaml +7 -0
  66. data/synth/001/output.log +44 -0
  67. data/synth/Makefile +39 -0
  68. data/synth/README.md +342 -0
  69. metadata +37 -10
  70. data/lib/language_operator/dsl/workflow_definition.rb +0 -259
  71. data/test_agent_dsl.rb +0 -108
@@ -0,0 +1,594 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'k8s-ruby'
4
+ require_relative 'base'
5
+ require_relative '../cli/formatters/progress_formatter'
6
+ require_relative '../config/cluster_config'
7
+ require_relative '../kubernetes/client'
8
+ require_relative '../kubernetes/resource_builder'
9
+
10
+ module LanguageOperator
11
+ module Ux
12
+ # Interactive quickstart wizard for first-time setup
13
+ #
14
+ # Special case: Does not require cluster selection (creates/selects cluster during flow).
15
+ #
16
+ # @example
17
+ # Ux::Quickstart.execute
18
+ #
19
+ # rubocop:disable Metrics/ClassLength, Metrics/MethodLength, Metrics/AbcSize, Naming/PredicateMethod
20
+ class Quickstart < Base
21
+ # Execute the quickstart flow
22
+ #
23
+ # @return [Boolean] true if quickstart completed successfully
24
+ def execute
25
+ show_welcome
26
+
27
+ # Step 1: Cluster setup
28
+ cluster_info = setup_cluster
29
+ return false unless cluster_info
30
+
31
+ # Step 2: Model configuration
32
+ model_info = configure_model(cluster_info)
33
+ return false unless model_info
34
+
35
+ # Step 3: Example agent
36
+ agent_created = create_example_agent(cluster_info, model_info)
37
+
38
+ # Show next steps
39
+ show_next_steps(agent_created: agent_created)
40
+
41
+ true
42
+ end
43
+
44
+ private
45
+
46
+ # Override: Quickstart does not require pre-selected cluster
47
+ def requires_cluster?
48
+ false
49
+ end
50
+
51
+ def show_welcome
52
+ puts
53
+ puts pastel.cyan('╭────────────────────────────────────────────────╮')
54
+ puts "#{pastel.cyan('│')} Welcome to Language Operator! 🎉 #{pastel.cyan('│')}"
55
+ puts "#{pastel.cyan('│')} Let's get you set up (takes ~5 minutes) #{pastel.cyan('│')}"
56
+ puts pastel.cyan('╰────────────────────────────────────────────────╯')
57
+ puts
58
+ puts 'This wizard will help you:'
59
+ puts ' 1. Connect to your Kubernetes cluster'
60
+ puts ' 2. Configure a language model'
61
+ puts ' 3. Create your first autonomous agent'
62
+ puts
63
+ puts pastel.dim('Press Enter to begin...')
64
+ $stdin.gets
65
+ end
66
+
67
+ def setup_cluster
68
+ puts
69
+ puts '─' * 50
70
+ puts pastel.cyan('Step 1/3: Connect to Kubernetes')
71
+ puts '─' * 50
72
+ puts
73
+
74
+ # Check if user has kubectl configured
75
+ has_kubectl = prompt.yes?('Do you have kubectl configured?')
76
+
77
+ unless has_kubectl
78
+ show_kubernetes_setup_guide
79
+ return nil
80
+ end
81
+
82
+ # Get available contexts
83
+ contexts = get_kubectl_contexts
84
+
85
+ if contexts.empty?
86
+ CLI::Formatters::ProgressFormatter.error('No kubectl contexts found')
87
+ puts
88
+ puts 'Configure kubectl first, then run quickstart again.'
89
+ return nil
90
+ end
91
+
92
+ puts
93
+ puts 'Great! I found these contexts in your kubeconfig:'
94
+ puts
95
+
96
+ # Let user select context
97
+ context = prompt.select('Which context should we use?', contexts)
98
+
99
+ # Generate cluster name
100
+ cluster_name = prompt.ask('Name for this cluster:', default: 'my-cluster') do |q|
101
+ q.required true
102
+ q.validate(/^[a-z0-9-]+$/, 'Name must be lowercase alphanumeric with hyphens')
103
+ end
104
+
105
+ # Create cluster configuration
106
+ create_cluster_config(cluster_name, context)
107
+ end
108
+
109
+ def get_kubectl_contexts
110
+ kubeconfig_path = ENV.fetch('KUBECONFIG', File.expand_path('~/.kube/config'))
111
+
112
+ return [] unless File.exist?(kubeconfig_path)
113
+
114
+ config = K8s::Config.load_file(kubeconfig_path)
115
+ config.contexts.map(&:name)
116
+ rescue StandardError => e
117
+ CLI::Formatters::ProgressFormatter.error("Failed to load kubeconfig: #{e.message}")
118
+ []
119
+ end
120
+
121
+ def create_cluster_config(name, context)
122
+ kubeconfig_path = ENV.fetch('KUBECONFIG', File.expand_path('~/.kube/config'))
123
+
124
+ CLI::Formatters::ProgressFormatter.with_spinner("Creating cluster '#{name}'") do
125
+ # Create Kubernetes client to verify connection
126
+ k8s = Kubernetes::Client.new(kubeconfig: kubeconfig_path, context: context)
127
+
128
+ # Get namespace from context or use default
129
+ namespace = k8s.current_namespace || 'default'
130
+
131
+ # Check if operator is installed
132
+ unless k8s.operator_installed?
133
+ puts
134
+ CLI::Formatters::ProgressFormatter.warn('Language Operator not found in cluster')
135
+ puts
136
+ puts 'The operator needs to be installed first.'
137
+ puts 'Install with:'
138
+ puts ' aictl install'
139
+ puts
140
+ exit 1
141
+ end
142
+
143
+ # Save cluster config
144
+ Config::ClusterConfig.add_cluster(name, namespace, kubeconfig_path, context)
145
+ Config::ClusterConfig.set_current_cluster(name)
146
+
147
+ { name: name, namespace: namespace, kubeconfig: kubeconfig_path, context: context, k8s: k8s }
148
+ end
149
+
150
+ {
151
+ name: name,
152
+ namespace: (kubeconfig_path && K8s::Config.load_file(kubeconfig_path).context(context).namespace) || 'default',
153
+ kubeconfig: kubeconfig_path,
154
+ context: context
155
+ }
156
+ rescue StandardError => e
157
+ puts
158
+ CLI::Formatters::ProgressFormatter.error("Failed to connect: #{e.message}")
159
+ nil
160
+ end
161
+
162
+ def show_kubernetes_setup_guide
163
+ puts
164
+ puts pastel.yellow('Kubernetes Setup Required')
165
+ puts
166
+ puts 'Language Operator needs a Kubernetes cluster to run.'
167
+ puts
168
+ puts 'Quick options:'
169
+ puts
170
+ puts ' 1. Docker Desktop (easiest for local development)'
171
+ puts ' • Enable Kubernetes in Docker Desktop settings'
172
+ puts ' • kubectl will be configured automatically'
173
+ puts
174
+ puts ' 2. Minikube (lightweight local cluster)'
175
+ puts ' • Install: brew install minikube'
176
+ puts ' • Start: minikube start'
177
+ puts
178
+ puts ' 3. Kind (Kubernetes in Docker)'
179
+ puts ' • Install: brew install kind'
180
+ puts ' • Create cluster: kind create cluster'
181
+ puts
182
+ puts 'After setting up kubectl, run quickstart again:'
183
+ puts ' aictl quickstart'
184
+ puts
185
+ end
186
+
187
+ def configure_model(cluster_info)
188
+ puts
189
+ puts '─' * 50
190
+ puts pastel.cyan('Step 2/3: Configure Language Model')
191
+ puts '─' * 50
192
+ puts
193
+ puts 'Agents need an LLM to understand instructions.'
194
+ puts
195
+
196
+ # Provider selection
197
+ provider = prompt.select('Which provider do you want to use?') do |menu|
198
+ menu.choice 'Anthropic (Claude)', :anthropic
199
+ menu.choice 'OpenAI (GPT-4)', :openai
200
+ menu.choice 'Local model (Ollama)', :ollama
201
+ menu.choice 'Other', :other
202
+ end
203
+
204
+ case provider
205
+ when :anthropic
206
+ setup_anthropic_model(cluster_info)
207
+ when :openai
208
+ setup_openai_model(cluster_info)
209
+ when :ollama
210
+ setup_ollama_model(cluster_info)
211
+ when :other
212
+ setup_custom_model(cluster_info)
213
+ end
214
+ end
215
+
216
+ def setup_anthropic_model(cluster_info)
217
+ puts
218
+ has_key = prompt.yes?('Do you have an Anthropic API key?')
219
+
220
+ unless has_key
221
+ puts
222
+ puts "Get an API key at: #{pastel.cyan('https://console.anthropic.com')}"
223
+ puts
224
+ puts pastel.dim('Press Enter when you have your key...')
225
+ $stdin.gets
226
+ end
227
+
228
+ puts
229
+ api_key = prompt.mask('Enter your Anthropic API key:')
230
+
231
+ # Test connection
232
+ test_result = CLI::Formatters::ProgressFormatter.with_spinner('Testing connection') do
233
+ test_anthropic_connection(api_key)
234
+ end
235
+
236
+ unless test_result[:success]
237
+ CLI::Formatters::ProgressFormatter.error("Connection failed: #{test_result[:error]}")
238
+ return nil
239
+ end
240
+
241
+ # Create model resource
242
+ model_name = 'claude'
243
+ model_id = 'claude-3-5-sonnet-20241022'
244
+
245
+ create_model_resource(cluster_info, model_name, 'anthropic', model_id, api_key)
246
+
247
+ { name: model_name, provider: 'anthropic', model: model_id }
248
+ end
249
+
250
+ def setup_openai_model(cluster_info)
251
+ puts
252
+ has_key = prompt.yes?('Do you have an OpenAI API key?')
253
+
254
+ unless has_key
255
+ puts
256
+ puts "Get an API key at: #{pastel.cyan('https://platform.openai.com/api-keys')}"
257
+ puts
258
+ puts pastel.dim('Press Enter when you have your key...')
259
+ $stdin.gets
260
+ end
261
+
262
+ puts
263
+ api_key = prompt.mask('Enter your OpenAI API key:')
264
+
265
+ # Test connection
266
+ test_result = CLI::Formatters::ProgressFormatter.with_spinner('Testing connection') do
267
+ test_openai_connection(api_key)
268
+ end
269
+
270
+ unless test_result[:success]
271
+ CLI::Formatters::ProgressFormatter.error("Connection failed: #{test_result[:error]}")
272
+ return nil
273
+ end
274
+
275
+ # Create model resource
276
+ model_name = 'gpt4'
277
+ model_id = 'gpt-4-turbo'
278
+
279
+ create_model_resource(cluster_info, model_name, 'openai', model_id, api_key)
280
+
281
+ { name: model_name, provider: 'openai', model: model_id }
282
+ end
283
+
284
+ def setup_ollama_model(cluster_info)
285
+ puts
286
+ puts 'Ollama runs LLMs locally on your machine.'
287
+ puts
288
+
289
+ endpoint = prompt.ask('Ollama endpoint:', default: 'http://localhost:11434')
290
+ model_id = prompt.ask('Model name:', default: 'llama3')
291
+
292
+ # Test connection
293
+ test_result = CLI::Formatters::ProgressFormatter.with_spinner('Testing connection') do
294
+ test_ollama_connection(endpoint, model_id)
295
+ end
296
+
297
+ unless test_result[:success]
298
+ CLI::Formatters::ProgressFormatter.error("Connection failed: #{test_result[:error]}")
299
+ puts
300
+ puts 'Make sure Ollama is running and the model is pulled:'
301
+ puts " ollama pull #{model_id}"
302
+ return nil
303
+ end
304
+
305
+ # Create model resource
306
+ model_name = 'local'
307
+
308
+ create_model_resource(cluster_info, model_name, 'openai-compatible', model_id, nil, endpoint)
309
+
310
+ { name: model_name, provider: 'ollama', model: model_id }
311
+ end
312
+
313
+ def setup_custom_model(cluster_info)
314
+ puts
315
+ puts 'Configure a custom OpenAI-compatible endpoint.'
316
+ puts
317
+
318
+ endpoint = prompt.ask('API endpoint URL:') do |q|
319
+ q.required true
320
+ q.validate(%r{^https?://})
321
+ q.messages[:valid?] = 'Must be a valid HTTP(S) URL'
322
+ end
323
+
324
+ requires_auth = prompt.yes?('Does this endpoint require authentication?')
325
+
326
+ api_key = nil
327
+ api_key = prompt.mask('Enter API key:') if requires_auth
328
+
329
+ # Try to fetch available models from the endpoint
330
+ puts
331
+ available_models = fetch_available_models(endpoint, api_key)
332
+
333
+ model_id = if available_models && !available_models.empty?
334
+ prompt.select('Select a model:', available_models, per_page: 10)
335
+ else
336
+ prompt.ask('Model identifier:') do |q|
337
+ q.required true
338
+ end
339
+ end
340
+
341
+ puts
342
+ CLI::Formatters::ProgressFormatter.info('Skipping connection test for custom endpoint')
343
+
344
+ # Create model resource
345
+ model_name = 'custom'
346
+
347
+ create_model_resource(cluster_info, model_name, 'openai-compatible', model_id, api_key, endpoint)
348
+
349
+ { name: model_name, provider: 'custom', model: model_id }
350
+ end
351
+
352
+ def test_anthropic_connection(api_key)
353
+ require 'ruby_llm'
354
+
355
+ client = RubyLLM.new(provider: :anthropic, api_key: api_key)
356
+ client.chat([{ role: 'user', content: 'Test' }], model: 'claude-3-5-sonnet-20241022', max_tokens: 10)
357
+
358
+ { success: true }
359
+ rescue StandardError => e
360
+ { success: false, error: e.message }
361
+ end
362
+
363
+ def test_openai_connection(api_key)
364
+ require 'ruby_llm'
365
+
366
+ client = RubyLLM.new(provider: :openai, api_key: api_key)
367
+ client.chat([{ role: 'user', content: 'Test' }], model: 'gpt-4-turbo', max_tokens: 10)
368
+
369
+ { success: true }
370
+ rescue StandardError => e
371
+ { success: false, error: e.message }
372
+ end
373
+
374
+ def test_ollama_connection(endpoint, model)
375
+ require 'ruby_llm'
376
+
377
+ client = RubyLLM.new(provider: :openai_compatible, url: endpoint)
378
+ client.chat([{ role: 'user', content: 'Test' }], model: model, max_tokens: 10)
379
+
380
+ { success: true }
381
+ rescue StandardError => e
382
+ { success: false, error: e.message }
383
+ end
384
+
385
+ def fetch_available_models(endpoint, api_key = nil)
386
+ require 'net/http'
387
+ require 'json'
388
+ require 'uri'
389
+
390
+ models_url = URI.join(endpoint, '/v1/models').to_s
391
+
392
+ models = nil
393
+ count = 0
394
+
395
+ CLI::Formatters::ProgressFormatter.with_spinner('Fetching available models') do
396
+ uri = URI(models_url)
397
+ request = Net::HTTP::Get.new(uri)
398
+ request['Authorization'] = "Bearer #{api_key}" if api_key
399
+ request['Content-Type'] = 'application/json'
400
+
401
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
402
+ http.request(request)
403
+ end
404
+
405
+ if response.is_a?(Net::HTTPSuccess)
406
+ data = JSON.parse(response.body)
407
+ # Extract model IDs from the response
408
+ models = data['data']&.map { |m| m['id'] } || []
409
+ count = models.size
410
+ else
411
+ CLI::Formatters::ProgressFormatter.warn("Could not fetch models (HTTP #{response.code})")
412
+ end
413
+ end
414
+
415
+ # Show count after spinner completes
416
+ puts pastel.dim("Found #{count} models") if count.positive?
417
+ models
418
+ rescue StandardError => e
419
+ CLI::Formatters::ProgressFormatter.warn("Could not fetch models: #{e.message}")
420
+ nil
421
+ end
422
+
423
+ def create_model_resource(cluster_info, name, provider, model, api_key = nil, endpoint = nil)
424
+ CLI::Formatters::ProgressFormatter.with_spinner("Creating model '#{name}'") do
425
+ k8s = Kubernetes::Client.new(
426
+ kubeconfig: cluster_info[:kubeconfig],
427
+ context: cluster_info[:context]
428
+ )
429
+
430
+ # Create API key secret if provided
431
+ if api_key
432
+ secret_name = "#{name}-api-key"
433
+ secret = {
434
+ 'apiVersion' => 'v1',
435
+ 'kind' => 'Secret',
436
+ 'metadata' => {
437
+ 'name' => secret_name,
438
+ 'namespace' => cluster_info[:namespace]
439
+ },
440
+ 'type' => 'Opaque',
441
+ 'stringData' => {
442
+ 'api-key' => api_key
443
+ }
444
+ }
445
+ k8s.apply_resource(secret)
446
+ end
447
+
448
+ # Create LanguageModel resource
449
+ resource = Kubernetes::ResourceBuilder.language_model(
450
+ name,
451
+ provider: provider,
452
+ model: model,
453
+ endpoint: endpoint,
454
+ cluster: cluster_info[:namespace]
455
+ )
456
+
457
+ # Add API key reference if we created a secret
458
+ if api_key
459
+ resource['spec']['apiKeySecret'] = {
460
+ 'name' => "#{name}-api-key",
461
+ 'key' => 'api-key'
462
+ }
463
+ end
464
+
465
+ k8s.apply_resource(resource)
466
+ end
467
+ # rubocop:enable Metrics/BlockLength
468
+ end
469
+
470
+ def create_example_agent(cluster_info, model_info)
471
+ puts
472
+ puts '─' * 50
473
+ puts pastel.cyan('Step 3/3: Create Your First Agent')
474
+ puts '─' * 50
475
+ puts
476
+
477
+ # Ask if user wants to create an example agent
478
+ create_agent = prompt.yes?('Would you like to create a simple agent to see how things work?')
479
+
480
+ unless create_agent
481
+ puts
482
+ puts pastel.dim('Skipping example agent creation.')
483
+ puts
484
+ return false
485
+ end
486
+
487
+ puts
488
+ puts "I'll create an agent that tells you fun facts about Ruby."
489
+ puts
490
+
491
+ agent_name = 'ruby-facts'
492
+ description = 'Tell me an interesting fun fact about the Ruby programming language'
493
+
494
+ k8s = Kubernetes::Client.new(
495
+ kubeconfig: cluster_info[:kubeconfig],
496
+ context: cluster_info[:context]
497
+ )
498
+
499
+ # Create agent
500
+ CLI::Formatters::ProgressFormatter.with_spinner("Creating agent '#{agent_name}'") do
501
+ resource = Kubernetes::ResourceBuilder.language_agent(
502
+ agent_name,
503
+ instructions: description,
504
+ cluster: cluster_info[:namespace],
505
+ persona: nil,
506
+ tools: [],
507
+ models: [model_info[:name]]
508
+ )
509
+
510
+ k8s.apply_resource(resource)
511
+ end
512
+
513
+ # Wait for synthesis
514
+ wait_for_synthesis(k8s, agent_name, cluster_info[:namespace])
515
+
516
+ true
517
+ end
518
+ # rubocop:enable Naming/PredicateMethod
519
+
520
+ def wait_for_synthesis(k8s, agent_name, namespace)
521
+ max_wait = 300 # 5 minutes for quickstart
522
+ interval = 2
523
+ elapsed = 0
524
+
525
+ CLI::Formatters::ProgressFormatter.with_spinner('Synthesizing code') do
526
+ loop do
527
+ agent = k8s.get_resource('LanguageAgent', agent_name, namespace)
528
+ conditions = agent.dig('status', 'conditions') || []
529
+ synthesized = conditions.find { |c| c['type'] == 'Synthesized' }
530
+
531
+ if synthesized
532
+ if synthesized['status'] == 'True'
533
+ break # Success
534
+ elsif synthesized['status'] == 'False'
535
+ raise StandardError, "Synthesis failed: #{synthesized['message']}"
536
+ end
537
+ end
538
+
539
+ if elapsed >= max_wait
540
+ CLI::Formatters::ProgressFormatter.warn('Synthesis is taking longer than expected, continuing in background')
541
+ break
542
+ end
543
+
544
+ sleep interval
545
+ elapsed += interval
546
+ end
547
+ end
548
+
549
+ puts
550
+ rescue K8s::Error::NotFound
551
+ # Agent not found yet, retry
552
+ retry if elapsed < max_wait
553
+ raise
554
+ end
555
+
556
+ def show_next_steps(agent_created: false)
557
+ puts
558
+ puts pastel.cyan("What's Next?")
559
+ puts
560
+
561
+ if agent_created
562
+ puts '1. Check your agent status:'
563
+ puts " #{pastel.dim('aictl agent inspect ruby-facts')}"
564
+ puts
565
+ puts '2. View the agent output:'
566
+ puts " #{pastel.dim('aictl agent logs ruby-facts')}"
567
+ puts
568
+ puts '3. Create another agent:'
569
+ puts " #{pastel.dim('aictl agent create "your task here"')}"
570
+ puts
571
+ puts '4. View all your agents:'
572
+ puts " #{pastel.dim('aictl agent list')}"
573
+ else
574
+ puts '1. Create your own agent:'
575
+ puts " #{pastel.dim('aictl agent create "your task here"')}"
576
+ puts
577
+ puts '2. Use the interactive wizard:'
578
+ puts " #{pastel.dim('aictl agent create --wizard')}"
579
+ puts
580
+ puts '3. View your agents:'
581
+ puts " #{pastel.dim('aictl agent list')}"
582
+ puts
583
+ puts '4. Check agent status:'
584
+ puts " #{pastel.dim('aictl agent inspect <agent-name>')}"
585
+ end
586
+
587
+ puts
588
+ puts pastel.green('Welcome to autonomous automation! 🚀')
589
+ puts
590
+ end
591
+ end
592
+ # rubocop:enable Metrics/ClassLength, Metrics/MethodLength, Metrics/AbcSize
593
+ end
594
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LanguageOperator
4
- VERSION = '0.1.31'
4
+ VERSION = '0.1.35'
5
5
  end
@@ -2,9 +2,11 @@
2
2
 
3
3
  require 'language_operator/version'
4
4
  require 'language_operator/errors'
5
+ require 'language_operator/constants'
5
6
  require 'language_operator/retry'
6
7
  require 'language_operator/config'
7
8
  require 'language_operator/validators'
9
+ require 'language_operator/type_coercion'
8
10
  require 'language_operator/dsl'
9
11
  require 'language_operator/client'
10
12
  require 'language_operator/tool_loader'
@@ -0,0 +1 @@
1
+ ../../ARCHITECTURE.md