language-operator 0.0.1 → 0.1.30

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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +125 -0
  3. data/CHANGELOG.md +53 -0
  4. data/Gemfile +8 -0
  5. data/Gemfile.lock +284 -0
  6. data/LICENSE +229 -21
  7. data/Makefile +77 -0
  8. data/README.md +3 -11
  9. data/Rakefile +34 -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/agent-reference.md +591 -0
  16. data/docs/dsl/best-practices.md +1078 -0
  17. data/docs/dsl/chat-endpoints.md +895 -0
  18. data/docs/dsl/constraints.md +671 -0
  19. data/docs/dsl/mcp-integration.md +1177 -0
  20. data/docs/dsl/webhooks.md +932 -0
  21. data/docs/dsl/workflows.md +744 -0
  22. data/examples/README.md +569 -0
  23. data/examples/agent_example.rb +86 -0
  24. data/examples/chat_endpoint_agent.rb +118 -0
  25. data/examples/github_webhook_agent.rb +171 -0
  26. data/examples/mcp_agent.rb +158 -0
  27. data/examples/oauth_callback_agent.rb +296 -0
  28. data/examples/stripe_webhook_agent.rb +219 -0
  29. data/examples/webhook_agent.rb +80 -0
  30. data/lib/language_operator/agent/base.rb +110 -0
  31. data/lib/language_operator/agent/executor.rb +440 -0
  32. data/lib/language_operator/agent/instrumentation.rb +54 -0
  33. data/lib/language_operator/agent/metrics_tracker.rb +183 -0
  34. data/lib/language_operator/agent/safety/ast_validator.rb +272 -0
  35. data/lib/language_operator/agent/safety/audit_logger.rb +104 -0
  36. data/lib/language_operator/agent/safety/budget_tracker.rb +175 -0
  37. data/lib/language_operator/agent/safety/content_filter.rb +93 -0
  38. data/lib/language_operator/agent/safety/manager.rb +207 -0
  39. data/lib/language_operator/agent/safety/rate_limiter.rb +150 -0
  40. data/lib/language_operator/agent/safety/safe_executor.rb +115 -0
  41. data/lib/language_operator/agent/scheduler.rb +183 -0
  42. data/lib/language_operator/agent/telemetry.rb +116 -0
  43. data/lib/language_operator/agent/web_server.rb +610 -0
  44. data/lib/language_operator/agent/webhook_authenticator.rb +226 -0
  45. data/lib/language_operator/agent.rb +149 -0
  46. data/lib/language_operator/cli/commands/agent.rb +1252 -0
  47. data/lib/language_operator/cli/commands/cluster.rb +335 -0
  48. data/lib/language_operator/cli/commands/install.rb +404 -0
  49. data/lib/language_operator/cli/commands/model.rb +266 -0
  50. data/lib/language_operator/cli/commands/persona.rb +396 -0
  51. data/lib/language_operator/cli/commands/quickstart.rb +22 -0
  52. data/lib/language_operator/cli/commands/status.rb +156 -0
  53. data/lib/language_operator/cli/commands/tool.rb +537 -0
  54. data/lib/language_operator/cli/commands/use.rb +47 -0
  55. data/lib/language_operator/cli/errors/handler.rb +180 -0
  56. data/lib/language_operator/cli/errors/suggestions.rb +176 -0
  57. data/lib/language_operator/cli/formatters/code_formatter.rb +81 -0
  58. data/lib/language_operator/cli/formatters/log_formatter.rb +290 -0
  59. data/lib/language_operator/cli/formatters/progress_formatter.rb +53 -0
  60. data/lib/language_operator/cli/formatters/table_formatter.rb +179 -0
  61. data/lib/language_operator/cli/formatters/value_formatter.rb +113 -0
  62. data/lib/language_operator/cli/helpers/cluster_context.rb +62 -0
  63. data/lib/language_operator/cli/helpers/cluster_validator.rb +101 -0
  64. data/lib/language_operator/cli/helpers/editor_helper.rb +58 -0
  65. data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +167 -0
  66. data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +74 -0
  67. data/lib/language_operator/cli/helpers/schedule_builder.rb +108 -0
  68. data/lib/language_operator/cli/helpers/user_prompts.rb +69 -0
  69. data/lib/language_operator/cli/main.rb +232 -0
  70. data/lib/language_operator/cli/templates/tools/generic.yaml +66 -0
  71. data/lib/language_operator/cli/wizards/agent_wizard.rb +246 -0
  72. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +588 -0
  73. data/lib/language_operator/client/base.rb +214 -0
  74. data/lib/language_operator/client/config.rb +136 -0
  75. data/lib/language_operator/client/cost_calculator.rb +37 -0
  76. data/lib/language_operator/client/mcp_connector.rb +123 -0
  77. data/lib/language_operator/client.rb +19 -0
  78. data/lib/language_operator/config/cluster_config.rb +101 -0
  79. data/lib/language_operator/config/tool_patterns.yaml +57 -0
  80. data/lib/language_operator/config/tool_registry.rb +96 -0
  81. data/lib/language_operator/config.rb +138 -0
  82. data/lib/language_operator/dsl/adapter.rb +124 -0
  83. data/lib/language_operator/dsl/agent_context.rb +90 -0
  84. data/lib/language_operator/dsl/agent_definition.rb +427 -0
  85. data/lib/language_operator/dsl/chat_endpoint_definition.rb +115 -0
  86. data/lib/language_operator/dsl/config.rb +119 -0
  87. data/lib/language_operator/dsl/context.rb +50 -0
  88. data/lib/language_operator/dsl/execution_context.rb +47 -0
  89. data/lib/language_operator/dsl/helpers.rb +109 -0
  90. data/lib/language_operator/dsl/http.rb +184 -0
  91. data/lib/language_operator/dsl/mcp_server_definition.rb +73 -0
  92. data/lib/language_operator/dsl/parameter_definition.rb +124 -0
  93. data/lib/language_operator/dsl/registry.rb +36 -0
  94. data/lib/language_operator/dsl/shell.rb +125 -0
  95. data/lib/language_operator/dsl/tool_definition.rb +112 -0
  96. data/lib/language_operator/dsl/webhook_authentication.rb +114 -0
  97. data/lib/language_operator/dsl/webhook_definition.rb +106 -0
  98. data/lib/language_operator/dsl/workflow_definition.rb +259 -0
  99. data/lib/language_operator/dsl.rb +160 -0
  100. data/lib/language_operator/errors.rb +60 -0
  101. data/lib/language_operator/kubernetes/client.rb +279 -0
  102. data/lib/language_operator/kubernetes/resource_builder.rb +194 -0
  103. data/lib/language_operator/loggable.rb +47 -0
  104. data/lib/language_operator/logger.rb +141 -0
  105. data/lib/language_operator/retry.rb +123 -0
  106. data/lib/language_operator/retryable.rb +132 -0
  107. data/lib/language_operator/tool_loader.rb +242 -0
  108. data/lib/language_operator/validators.rb +170 -0
  109. data/lib/language_operator/version.rb +1 -1
  110. data/lib/language_operator.rb +65 -3
  111. data/requirements/tasks/challenge.md +9 -0
  112. data/requirements/tasks/iterate.md +36 -0
  113. data/requirements/tasks/optimize.md +21 -0
  114. data/requirements/tasks/tag.md +5 -0
  115. data/test_agent_dsl.rb +108 -0
  116. metadata +503 -20
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pastel'
4
+ require_relative 'suggestions'
5
+ require_relative '../formatters/progress_formatter'
6
+
7
+ module LanguageOperator
8
+ module CLI
9
+ module Errors
10
+ # Central error handler with context-aware suggestions
11
+ class Handler
12
+ class << self
13
+ # Handle an error with context and provide helpful suggestions
14
+ def handle(error, context = {})
15
+ case error
16
+ when K8s::Error::NotFound
17
+ handle_not_found(error, context)
18
+ else
19
+ handle_generic(error, context)
20
+ end
21
+ end
22
+
23
+ # Handle resource not found errors with fuzzy matching
24
+ def handle_not_found(error, context)
25
+ resource_type = context[:resource_type] || 'Resource'
26
+ resource_name = context[:resource_name] || 'unknown'
27
+ cluster = context[:cluster]
28
+
29
+ # Display main error message
30
+ message = if cluster
31
+ "#{format_resource_type(resource_type)} '#{resource_name}' not found in cluster '#{cluster}'"
32
+ else
33
+ "#{format_resource_type(resource_type)} '#{resource_name}' not found"
34
+ end
35
+
36
+ Formatters::ProgressFormatter.error(message)
37
+ puts
38
+
39
+ # Find and display similar resources
40
+ if context[:available_resources]
41
+ similar = Suggestions.find_similar(resource_name, context[:available_resources])
42
+ if similar.any?
43
+ puts pastel.yellow('Did you mean?')
44
+ similar.each { |name| puts " #{pastel.cyan('•')} #{name}" }
45
+ puts
46
+ end
47
+ end
48
+
49
+ # Display recovery suggestions
50
+ error_type = error_type_for_resource(resource_type)
51
+ suggestions = Suggestions.for_error(error_type, context)
52
+ display_suggestions(suggestions) if suggestions.any?
53
+
54
+ # Re-raise if in debug mode, otherwise exit
55
+ raise error if ENV['DEBUG']
56
+
57
+ exit 1
58
+ end
59
+
60
+ # Handle generic errors
61
+ def handle_generic(error, context)
62
+ operation = context[:operation] || 'operation'
63
+
64
+ Formatters::ProgressFormatter.error("Failed to #{operation}: #{error.message}")
65
+ puts
66
+
67
+ # Display suggestions if provided in context
68
+ display_suggestions(context[:suggestions]) if context[:suggestions]
69
+
70
+ # Re-raise if in debug mode, otherwise exit
71
+ raise error if ENV['DEBUG']
72
+
73
+ exit 1
74
+ end
75
+
76
+ # Handle specific error scenarios with custom suggestions
77
+ def handle_no_cluster_selected
78
+ Formatters::ProgressFormatter.error('No cluster selected')
79
+ puts
80
+
81
+ puts 'You must connect to a cluster first:'
82
+ puts
83
+
84
+ suggestions = Suggestions.for_error(:no_cluster_selected)
85
+ suggestions.each { |line| puts line }
86
+
87
+ exit 1
88
+ end
89
+
90
+ def handle_no_models_available(context = {})
91
+ Formatters::ProgressFormatter.error('No models found in cluster')
92
+ puts
93
+
94
+ suggestions = Suggestions.for_error(:no_models_available, context)
95
+ suggestions.each { |line| puts line }
96
+
97
+ exit 1
98
+ end
99
+
100
+ def handle_synthesis_failed(message)
101
+ Formatters::ProgressFormatter.error("Synthesis failed: #{message}")
102
+ puts
103
+
104
+ suggestions = Suggestions.for_error(:synthesis_failed)
105
+ suggestions.each { |line| puts line }
106
+
107
+ exit 1
108
+ end
109
+
110
+ def handle_already_exists(context = {})
111
+ resource_type = context[:resource_type] || 'Resource'
112
+ resource_name = context[:resource_name] || 'unknown'
113
+ cluster = context[:cluster]
114
+
115
+ message = if cluster
116
+ "#{format_resource_type(resource_type)} '#{resource_name}' already exists in cluster '#{cluster}'"
117
+ else
118
+ "#{format_resource_type(resource_type)} '#{resource_name}' already exists"
119
+ end
120
+
121
+ Formatters::ProgressFormatter.error(message)
122
+ puts
123
+
124
+ suggestions = Suggestions.for_error(:already_exists, context)
125
+ display_suggestions(suggestions) if suggestions.any?
126
+
127
+ exit 1
128
+ end
129
+
130
+ private
131
+
132
+ def display_suggestions(suggestions)
133
+ return if suggestions.empty?
134
+
135
+ suggestions.each do |suggestion|
136
+ puts suggestion
137
+ end
138
+ puts
139
+ end
140
+
141
+ def format_resource_type(resource_type)
142
+ case resource_type
143
+ when 'LanguageAgent'
144
+ 'Agent'
145
+ when 'LanguageTool'
146
+ 'Tool'
147
+ when 'LanguageModel'
148
+ 'Model'
149
+ when 'LanguagePersona'
150
+ 'Persona'
151
+ else
152
+ resource_type
153
+ end
154
+ end
155
+
156
+ def error_type_for_resource(resource_type)
157
+ case resource_type
158
+ when 'LanguageAgent', 'agent'
159
+ :agent_not_found
160
+ when 'LanguageTool', 'tool'
161
+ :tool_not_found
162
+ when 'LanguageModel', 'model'
163
+ :model_not_found
164
+ when 'LanguagePersona', 'persona'
165
+ :persona_not_found
166
+ when 'cluster'
167
+ :cluster_not_found
168
+ else
169
+ :resource_not_found
170
+ end
171
+ end
172
+
173
+ def pastel
174
+ @pastel ||= Pastel.new
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'did_you_mean'
4
+ require 'pastel'
5
+
6
+ module LanguageOperator
7
+ module CLI
8
+ module Errors
9
+ # Provides helpful suggestions for error recovery
10
+ class Suggestions
11
+ class << self
12
+ # Find similar resource names using fuzzy matching
13
+ def find_similar(input_name, available_names, limit: 3)
14
+ return [] if available_names.empty?
15
+
16
+ spell_checker = DidYouMean::SpellChecker.new(dictionary: available_names)
17
+ corrections = spell_checker.correct(input_name)
18
+ corrections.first(limit)
19
+ end
20
+
21
+ # Generate context-aware suggestions based on error type
22
+ def for_error(error_type, context = {})
23
+ case error_type
24
+ when :agent_not_found
25
+ agent_not_found_suggestions(context)
26
+ when :tool_not_found
27
+ tool_not_found_suggestions(context)
28
+ when :model_not_found
29
+ model_not_found_suggestions(context)
30
+ when :persona_not_found
31
+ persona_not_found_suggestions(context)
32
+ when :cluster_not_found
33
+ cluster_not_found_suggestions(context)
34
+ when :no_cluster_selected
35
+ no_cluster_selected_suggestions
36
+ when :no_models_available
37
+ no_models_available_suggestions(context)
38
+ when :synthesis_failed
39
+ synthesis_failed_suggestions
40
+ when :already_exists
41
+ already_exists_suggestions(context)
42
+ else
43
+ []
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def agent_not_found_suggestions(context)
50
+ suggestions = []
51
+ suggestions << "List all agents: #{pastel.dim('aictl agent list')}"
52
+ suggestions << "Create a new agent: #{pastel.dim('aictl agent create \"description\"')}"
53
+ suggestions << "Use the wizard: #{pastel.dim('aictl agent create --wizard')}" if context[:suggest_wizard]
54
+ suggestions
55
+ end
56
+
57
+ def tool_not_found_suggestions(context)
58
+ suggestions = []
59
+ tool_name = context[:tool_name]
60
+
61
+ if tool_name
62
+ suggestions << "Install from registry: #{pastel.dim("aictl tool install #{tool_name}")}"
63
+ suggestions << "Search for tools: #{pastel.dim('aictl tool search')}"
64
+ end
65
+
66
+ suggestions << "List installed tools: #{pastel.dim('aictl tool list')}"
67
+ suggestions
68
+ end
69
+
70
+ def model_not_found_suggestions(_context)
71
+ suggestions = []
72
+ suggestions << "List all models: #{pastel.dim('aictl model list')}"
73
+ suggestions << "Create a new model: #{pastel.dim('aictl model create <name> --provider <provider>')}"
74
+ suggestions
75
+ end
76
+
77
+ def persona_not_found_suggestions(_context)
78
+ suggestions = []
79
+ suggestions << "List all personas: #{pastel.dim('aictl persona list')}"
80
+ suggestions << "Create a new persona: #{pastel.dim('aictl persona create <name>')}"
81
+ suggestions
82
+ end
83
+
84
+ def cluster_not_found_suggestions(_context)
85
+ suggestions = []
86
+ suggestions << "List available clusters: #{pastel.dim('aictl cluster list')}"
87
+ suggestions << "Create a new cluster: #{pastel.dim('aictl cluster create <name>')}"
88
+ suggestions
89
+ end
90
+
91
+ def no_cluster_selected_suggestions
92
+ [
93
+ 'Create a new cluster:',
94
+ " #{pastel.dim('aictl cluster create my-cluster')}",
95
+ '',
96
+ 'Or select an existing cluster:',
97
+ " #{pastel.dim('aictl use my-cluster')}",
98
+ '',
99
+ 'List available clusters:',
100
+ " #{pastel.dim('aictl cluster list')}"
101
+ ]
102
+ end
103
+
104
+ def no_models_available_suggestions(context)
105
+ cluster = context[:cluster]
106
+ suggestions = []
107
+
108
+ if cluster
109
+ suggestions << "Create a model in cluster '#{cluster}':"
110
+ suggestions << " #{pastel.dim('aictl model create <name> --provider anthropic --model claude-3-5-sonnet')}"
111
+ else
112
+ suggestions << "Create a model: #{pastel.dim('aictl model create <name> --provider <provider>')}"
113
+ end
114
+
115
+ suggestions << ''
116
+ suggestions << "List available models: #{pastel.dim('aictl model list')}"
117
+ suggestions
118
+ end
119
+
120
+ def synthesis_failed_suggestions
121
+ [
122
+ 'The description may be too vague. Try the interactive wizard:',
123
+ " #{pastel.dim('aictl agent create --wizard')}",
124
+ '',
125
+ 'Or provide a more detailed description with:',
126
+ ' • What the agent should do specifically',
127
+ ' • When it should run (schedule)',
128
+ ' • What tools or resources it needs',
129
+ '',
130
+ 'Examples:',
131
+ " #{pastel.green('✓')} #{pastel.dim('\"Check my email every hour and notify me of urgent messages\"')}",
132
+ " #{pastel.green('✓')} #{pastel.dim('\"Review my spreadsheet at 4pm daily and email me errors\"')}",
133
+ " #{pastel.red('✗')} #{pastel.dim('\"do the thing\"')} (too vague)"
134
+ ]
135
+ end
136
+
137
+ def already_exists_suggestions(context)
138
+ resource_type = context[:resource_type]
139
+ resource_name = context[:resource_name]
140
+
141
+ suggestions = []
142
+
143
+ if resource_name
144
+ suggestions << "View existing #{resource_type}: #{pastel.dim("aictl #{command_for_resource(resource_type)} inspect #{resource_name}")}"
145
+ suggestions << "Delete and recreate: #{pastel.dim("aictl #{command_for_resource(resource_type)} delete #{resource_name}")}"
146
+ end
147
+
148
+ suggestions << 'Choose a different name'
149
+ suggestions
150
+ end
151
+
152
+ def command_for_resource(resource_type)
153
+ case resource_type
154
+ when 'LanguageAgent', 'agent'
155
+ 'agent'
156
+ when 'LanguageTool', 'tool'
157
+ 'tool'
158
+ when 'LanguageModel', 'model'
159
+ 'model'
160
+ when 'LanguagePersona', 'persona'
161
+ 'persona'
162
+ when 'cluster'
163
+ 'cluster'
164
+ else
165
+ resource_type.downcase
166
+ end
167
+ end
168
+
169
+ def pastel
170
+ @pastel ||= Pastel.new
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rouge'
4
+ require 'pastel'
5
+
6
+ module LanguageOperator
7
+ module CLI
8
+ module Formatters
9
+ # Formatter for displaying syntax-highlighted code in the terminal
10
+ class CodeFormatter
11
+ class << self
12
+ # Display Ruby code with syntax highlighting
13
+ #
14
+ # @param code_content [String] The Ruby code to display
15
+ # @param title [String, nil] Optional title to display above the code
16
+ # @param max_lines [Integer, nil] Maximum number of lines to display (nil for all)
17
+ def display_ruby_code(code_content, title: nil, max_lines: nil)
18
+ # Use Rouge to highlight the code
19
+ formatter = Rouge::Formatters::Terminal256.new
20
+ lexer = Rouge::Lexers::Ruby.new
21
+
22
+ # Truncate if max_lines specified
23
+ lines = code_content.lines
24
+ truncated = false
25
+ if max_lines && lines.length > max_lines
26
+ code_to_display = lines[0...max_lines].join
27
+ truncated = true
28
+ remaining_lines = lines.length - max_lines
29
+ else
30
+ code_to_display = code_content
31
+ end
32
+
33
+ # Print header
34
+ puts
35
+ puts pastel.cyan(title) if title
36
+ puts pastel.dim('─' * 80)
37
+ puts
38
+
39
+ # Highlight and print the code
40
+ highlighted = formatter.format(lexer.lex(code_to_display))
41
+ puts highlighted
42
+
43
+ # Show truncation notice if applicable
44
+ if truncated
45
+ puts
46
+ puts pastel.dim("... #{remaining_lines} more lines ...")
47
+ end
48
+
49
+ # Print footer
50
+ puts
51
+ puts pastel.dim('─' * 80)
52
+ end
53
+
54
+ # Display a code snippet with context
55
+ #
56
+ # @param code_content [String] The Ruby code
57
+ # @param description [String] Description of what the code does
58
+ def display_snippet(code_content, description: nil)
59
+ puts
60
+ puts pastel.cyan('Generated Code Preview:') if description.nil?
61
+ puts pastel.dim(description) if description
62
+ puts
63
+
64
+ formatter = Rouge::Formatters::Terminal256.new
65
+ lexer = Rouge::Lexers::Ruby.new
66
+ highlighted = formatter.format(lexer.lex(code_content))
67
+
68
+ puts highlighted
69
+ puts
70
+ end
71
+
72
+ private
73
+
74
+ def pastel
75
+ @pastel ||= Pastel.new
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end