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,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LanguageOperator
4
+ module Dsl
5
+ # Execution context that includes helpers for tool execution
6
+ #
7
+ # Provides helper methods during tool execution, allowing tools to access
8
+ # HTTP, Shell, Config and other utilities directly.
9
+ #
10
+ # @example Using in tool execution
11
+ # context = LanguageOperator::Dsl::ExecutionContext.new(params)
12
+ # context.http_get('https://example.com')
13
+ # context.shell('ls -la')
14
+ class ExecutionContext
15
+ include LanguageOperator::Dsl::Helpers
16
+
17
+ # Provide access to HTTP and Shell helper classes as constants
18
+ HTTP = LanguageOperator::Dsl::HTTP
19
+ Shell = LanguageOperator::Dsl::Shell
20
+
21
+ # Initialize execution context with parameters
22
+ #
23
+ # @param params [Hash] Tool execution parameters
24
+ def initialize(params)
25
+ @params = params
26
+ end
27
+
28
+ # Forward missing methods to helpers
29
+ #
30
+ # @param method [Symbol] Method name
31
+ # @param args [Array] Method arguments
32
+ def method_missing(method, *args)
33
+ # Allow helper methods to be called directly
34
+ super
35
+ end
36
+
37
+ # Check if method is available
38
+ #
39
+ # @param method [Symbol] Method name
40
+ # @param include_private [Boolean] Include private methods
41
+ # @return [Boolean]
42
+ def respond_to_missing?(method, include_private = false)
43
+ LanguageOperator::Dsl::Helpers.instance_methods.include?(method) || super
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+ require 'shellwords'
5
+
6
+ module LanguageOperator
7
+ module Dsl
8
+ # Common helper methods for MCP tools
9
+ #
10
+ # Provides validation, formatting, and utility methods that can be used
11
+ # within tool execute blocks. All methods are instance methods that get
12
+ # mixed into the tool execution context.
13
+ #
14
+ # @example Using helpers in a tool
15
+ # tool "send_email" do
16
+ # execute do |params|
17
+ # error = validate_email(params["email"])
18
+ # return error if error
19
+ # # email is valid, proceed...
20
+ # end
21
+ # end
22
+ module Helpers
23
+ # Validate URL format
24
+ #
25
+ # @param url [String] URL to validate
26
+ # @return [String, nil] Error message if invalid, nil if valid
27
+ def validate_url(url)
28
+ return 'Error: Invalid URL. Must start with http:// or https://' unless url =~ %r{^https?://}
29
+
30
+ nil
31
+ end
32
+
33
+ # Validate phone number in E.164 format
34
+ #
35
+ # @param number [String] Phone number to validate
36
+ # @return [String, nil] Error message if invalid, nil if valid
37
+ def validate_phone(number)
38
+ return 'Error: Invalid phone number format. Use E.164 format (e.g., +1234567890)' unless number =~ /^\+\d{10,15}$/
39
+
40
+ nil
41
+ end
42
+
43
+ # Validate email address format
44
+ #
45
+ # @param email [String] Email address to validate
46
+ # @return [String, nil] Error message if invalid, nil if valid
47
+ def validate_email(email)
48
+ return 'Error: Invalid email format' unless email =~ /\A[\w+\-.]+@[a-z\d-]+(\.[a-z\d-]+)*\.[a-z]+\z/i
49
+
50
+ nil
51
+ end
52
+
53
+ # Shell escape for backtick execution (deprecated - use Shell.run instead)
54
+ # @deprecated This method is deprecated and will be removed for security reasons
55
+ def shell_escape(str)
56
+ warn 'DEPRECATION WARNING: shell_escape is deprecated and should not be used'
57
+ Shellwords.escape(str.to_s)
58
+ end
59
+
60
+ # Run a command and return structured result
61
+ # @deprecated This method has been removed for security reasons. Use Shell.run instead.
62
+ # @raise [SecurityError] Always raises an error
63
+ def run_command(_cmd)
64
+ raise SecurityError, 'run_command has been removed for security reasons. Use Shell.run instead.'
65
+ end
66
+
67
+ # Check required environment variables
68
+ def env_required(*vars)
69
+ missing = vars.reject { |v| ENV.fetch(v, nil) }
70
+ return "Error: Missing required environment variables: #{missing.join(', ')}" unless missing.empty?
71
+
72
+ nil
73
+ end
74
+
75
+ # Get environment variable with fallbacks
76
+ def env_get(*keys, default: nil)
77
+ keys.each do |key|
78
+ value = ENV.fetch(key, nil)
79
+ return value if value
80
+ end
81
+ default
82
+ end
83
+
84
+ # Truncate text to a maximum length
85
+ def truncate(text, max_length: 2000, suffix: '...')
86
+ return text if text.length <= max_length
87
+
88
+ text[0...max_length] + suffix
89
+ end
90
+
91
+ # Parse comma-separated values
92
+ def parse_csv(str)
93
+ return [] if str.nil? || str.empty?
94
+
95
+ str.split(',').map(&:strip).reject(&:empty?)
96
+ end
97
+
98
+ # Format error message
99
+ def error(message)
100
+ "Error: #{message}"
101
+ end
102
+
103
+ # Format success message
104
+ def success(message)
105
+ message
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+ require 'net/http'
5
+ require 'uri'
6
+ require 'json'
7
+
8
+ module LanguageOperator
9
+ module Dsl
10
+ # HTTP client helper for MCP tools
11
+ #
12
+ # Provides convenient methods for making HTTP requests from within tools.
13
+ # Supports GET, POST, PUT, DELETE, HEAD with automatic JSON parsing.
14
+ #
15
+ # @example Making a GET request
16
+ # result = HTTP.get('https://api.example.com/users')
17
+ # if result[:success]
18
+ # users = result[:json]
19
+ # end
20
+ class HTTP
21
+ # Perform a GET request
22
+ def self.get(url, headers: {}, follow_redirects: true, timeout: 30)
23
+ uri = parse_uri(url)
24
+ return { error: "Invalid URL: #{url}" } unless uri
25
+
26
+ http = build_http(uri, timeout: timeout)
27
+ request = Net::HTTP::Get.new(uri)
28
+ add_headers(request, headers)
29
+
30
+ execute_request(http, request, follow_redirects: follow_redirects)
31
+ end
32
+
33
+ # Perform a POST request
34
+ def self.post(url, body: nil, json: nil, headers: {}, auth: nil, timeout: 30)
35
+ uri = parse_uri(url)
36
+ return { error: "Invalid URL: #{url}" } unless uri
37
+
38
+ http = build_http(uri, timeout: timeout)
39
+ request = Net::HTTP::Post.new(uri)
40
+
41
+ # Set body
42
+ if json
43
+ request.body = json.to_json
44
+ request['Content-Type'] = 'application/json'
45
+ elsif body
46
+ request.body = body
47
+ end
48
+
49
+ add_headers(request, headers)
50
+ add_auth(request, auth) if auth
51
+
52
+ execute_request(http, request)
53
+ end
54
+
55
+ # Perform a PUT request
56
+ def self.put(url, body: nil, json: nil, headers: {}, auth: nil, timeout: 30)
57
+ uri = parse_uri(url)
58
+ return { error: "Invalid URL: #{url}" } unless uri
59
+
60
+ http = build_http(uri, timeout: timeout)
61
+ request = Net::HTTP::Put.new(uri)
62
+
63
+ if json
64
+ request.body = json.to_json
65
+ request['Content-Type'] = 'application/json'
66
+ elsif body
67
+ request.body = body
68
+ end
69
+
70
+ add_headers(request, headers)
71
+ add_auth(request, auth) if auth
72
+
73
+ execute_request(http, request)
74
+ end
75
+
76
+ # Perform a DELETE request
77
+ def self.delete(url, headers: {}, auth: nil, timeout: 30)
78
+ uri = parse_uri(url)
79
+ return { error: "Invalid URL: #{url}" } unless uri
80
+
81
+ http = build_http(uri, timeout: timeout)
82
+ request = Net::HTTP::Delete.new(uri)
83
+
84
+ add_headers(request, headers)
85
+ add_auth(request, auth) if auth
86
+
87
+ execute_request(http, request)
88
+ end
89
+
90
+ # Get just the headers from a URL
91
+ def self.head(url, headers: {}, timeout: 30)
92
+ uri = parse_uri(url)
93
+ return { error: "Invalid URL: #{url}" } unless uri
94
+
95
+ http = build_http(uri, timeout: timeout)
96
+ request = Net::HTTP::Head.new(uri)
97
+ add_headers(request, headers)
98
+
99
+ execute_request(http, request)
100
+ end
101
+
102
+ # Wrapper for curl commands (REMOVED FOR SECURITY)
103
+ # This method has been removed as it executes shell commands via backticks
104
+ # which is a security risk in synthesized code.
105
+ # @deprecated This method has been removed for security reasons
106
+ # @raise [SecurityError] Always raises an error
107
+ def self.curl(_url, options: [])
108
+ raise SecurityError, 'HTTP.curl has been removed for security reasons. Use HTTP.get, HTTP.post, etc. instead.'
109
+ end
110
+
111
+ class << self
112
+ private
113
+
114
+ def parse_uri(url)
115
+ URI.parse(url)
116
+ rescue URI::InvalidURIError
117
+ nil
118
+ end
119
+
120
+ def build_http(uri, timeout: 30)
121
+ http = Net::HTTP.new(uri.host, uri.port)
122
+ http.use_ssl = (uri.scheme == 'https')
123
+ http.open_timeout = timeout
124
+ http.read_timeout = timeout
125
+ http
126
+ end
127
+
128
+ def add_headers(request, headers)
129
+ # Add default user agent if not provided
130
+ request['User-Agent'] ||= 'Langop-SDK/1.0'
131
+
132
+ headers.each do |key, value|
133
+ request[key] = value
134
+ end
135
+ end
136
+
137
+ def add_auth(request, auth)
138
+ case auth[:type]
139
+ when :basic
140
+ request.basic_auth(auth[:username], auth[:password])
141
+ when :bearer
142
+ request['Authorization'] = "Bearer #{auth[:token]}"
143
+ when :token
144
+ request['Authorization'] = "Token #{auth[:token]}"
145
+ end
146
+ end
147
+
148
+ def execute_request(http, request, follow_redirects: false)
149
+ response = http.request(request)
150
+
151
+ # Handle redirects
152
+ if follow_redirects && response.is_a?(Net::HTTPRedirection)
153
+ location = response['location']
154
+ return get(location, headers: request.to_hash, follow_redirects: true)
155
+ end
156
+
157
+ # Parse response
158
+ result = {
159
+ status: response.code.to_i,
160
+ headers: response.to_hash,
161
+ body: response.body,
162
+ success: response.is_a?(Net::HTTPSuccess)
163
+ }
164
+
165
+ # Try to parse JSON if content-type indicates JSON
166
+ if response['content-type']&.include?('application/json')
167
+ begin
168
+ result[:json] = JSON.parse(response.body)
169
+ rescue JSON::ParserError
170
+ # Body is not valid JSON, leave as string
171
+ end
172
+ end
173
+
174
+ result
175
+ rescue StandardError => e
176
+ {
177
+ error: e.message,
178
+ success: false
179
+ }
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'tool_definition'
4
+
5
+ module LanguageOperator
6
+ module Dsl
7
+ # MCP server definition for agents
8
+ #
9
+ # Allows agents to expose their own tools via the MCP protocol.
10
+ # Tools defined here can be called by other agents or MCP clients.
11
+ #
12
+ # @example Define tools in an agent
13
+ # agent "data-processor" do
14
+ # as_mcp_server do
15
+ # tool "process_csv" do
16
+ # description "Process CSV data"
17
+ # parameter :url do
18
+ # type :string
19
+ # required true
20
+ # end
21
+ # execute do |params|
22
+ # # Processing logic
23
+ # end
24
+ # end
25
+ # end
26
+ # end
27
+ class McpServerDefinition
28
+ attr_reader :tools, :server_name
29
+
30
+ def initialize(agent_name)
31
+ @agent_name = agent_name
32
+ @server_name = "#{agent_name}-mcp"
33
+ @tools = {}
34
+ end
35
+
36
+ # Define a tool that this agent exposes
37
+ #
38
+ # @param name [String] Tool name
39
+ # @yield Tool definition block
40
+ # @return [ToolDefinition] The tool definition
41
+ def tool(name, &block)
42
+ tool_def = ToolDefinition.new(name)
43
+ tool_def.instance_eval(&block) if block
44
+ @tools[name] = tool_def
45
+ tool_def
46
+ end
47
+
48
+ # Set custom server name
49
+ #
50
+ # @param name [String] Server name
51
+ # @return [String] Current server name
52
+ def name(name = nil)
53
+ return @server_name if name.nil?
54
+
55
+ @server_name = name
56
+ end
57
+
58
+ # Get all tool definitions
59
+ #
60
+ # @return [Array<ToolDefinition>] Array of tool definitions
61
+ def all_tools
62
+ @tools.values
63
+ end
64
+
65
+ # Check if any tools are defined
66
+ #
67
+ # @return [Boolean] True if tools are defined
68
+ def tools?
69
+ !@tools.empty?
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LanguageOperator
4
+ module Dsl
5
+ # Parameter definition for tool parameters
6
+ #
7
+ # Defines parameter schema, validation, and metadata for MCP tools.
8
+ # Supports type checking, required validation, enums, defaults, and custom validators.
9
+ #
10
+ # @example Define a required string parameter
11
+ # parameter :name do
12
+ # type :string
13
+ # required true
14
+ # description "User's name"
15
+ # end
16
+ #
17
+ # @example Parameter with enum
18
+ # parameter :status do
19
+ # type :string
20
+ # enum ["active", "inactive", "pending"]
21
+ # default "pending"
22
+ # end
23
+ class ParameterDefinition
24
+ attr_reader :name, :validator
25
+
26
+ def initialize(name)
27
+ @name = name
28
+ @required = false
29
+ @validator = nil
30
+ end
31
+
32
+ def type(val = nil)
33
+ return @type if val.nil?
34
+
35
+ @type = val
36
+ end
37
+
38
+ # Setter method for required flag
39
+ def required(val = true)
40
+ @required = val
41
+ end
42
+
43
+ # Getter method for required flag (explicit to avoid collision with setter)
44
+ def required?
45
+ @required
46
+ end
47
+
48
+ def description(val = nil)
49
+ return @description if val.nil?
50
+
51
+ @description = val
52
+ end
53
+
54
+ def enum(val = nil)
55
+ return @enum if val.nil?
56
+
57
+ @enum = val
58
+ end
59
+
60
+ def default(val = nil)
61
+ return @default if val.nil?
62
+
63
+ @default = val
64
+ end
65
+
66
+ # Custom validation with proc or regex
67
+ def validate(proc_or_regex)
68
+ @validator = proc_or_regex
69
+ end
70
+
71
+ # Built-in validators
72
+ def url_format
73
+ @validator = %r{^https?://}
74
+ end
75
+
76
+ def email_format
77
+ @validator = /\A[\w+\-.]+@[a-z\d-]+(\.[a-z\d-]+)*\.[a-z]+\z/i
78
+ end
79
+
80
+ def phone_format
81
+ @validator = /^\+\d{10,15}$/
82
+ end
83
+
84
+ # Validate a value against this parameter's validator
85
+ def validate_value(value)
86
+ return nil unless @validator
87
+
88
+ case @validator
89
+ when Regexp
90
+ return "Parameter '#{@name}' has invalid format" unless value.to_s =~ @validator
91
+ when Proc
92
+ result = @validator.call(value)
93
+ return result if result.is_a?(String) # Error message
94
+ return "Parameter '#{@name}' validation failed" unless result
95
+ end
96
+
97
+ nil # No error
98
+ end
99
+
100
+ def to_schema
101
+ schema = {
102
+ 'type' => map_type(@type),
103
+ 'description' => @description
104
+ }
105
+ schema['enum'] = @enum if @enum
106
+ schema['default'] = @default if @default
107
+ schema
108
+ end
109
+
110
+ private
111
+
112
+ def map_type(ruby_type)
113
+ case ruby_type
114
+ when :string then 'string'
115
+ when :number, :integer then 'number'
116
+ when :boolean then 'boolean'
117
+ when :array then 'array'
118
+ when :object then 'object'
119
+ else 'string'
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LanguageOperator
4
+ module Dsl
5
+ # Registry for storing and retrieving tool definitions
6
+ #
7
+ # Manages a collection of tools defined using the DSL.
8
+ # Tools can be registered, retrieved by name, or accessed as a collection.
9
+ #
10
+ # @example Using the registry
11
+ # registry = Registry.new
12
+ # registry.register(tool_definition)
13
+ # all_tools = registry.all
14
+ class Registry
15
+ def initialize
16
+ @tools = {}
17
+ end
18
+
19
+ def register(tool)
20
+ @tools[tool.name] = tool
21
+ end
22
+
23
+ def get(name)
24
+ @tools[name]
25
+ end
26
+
27
+ def all
28
+ @tools.values
29
+ end
30
+
31
+ def clear
32
+ @tools.clear
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+ require 'open3'
5
+ require 'timeout'
6
+
7
+ module LanguageOperator
8
+ module Dsl
9
+ # Safe shell command execution for MCP tools
10
+ #
11
+ # Provides methods for executing shell commands safely with automatic
12
+ # argument escaping to prevent injection attacks. All methods are class methods.
13
+ #
14
+ # @example Basic usage
15
+ # result = Shell.run('ls', '-la', '/tmp')
16
+ # if result[:success]
17
+ # puts result[:output]
18
+ # end
19
+ #
20
+ # @example Safe user input
21
+ # # User input is automatically escaped
22
+ # result = Shell.run('grep', user_input, '/etc/hosts')
23
+ module Shell
24
+ # Run a shell command with properly escaped arguments
25
+ #
26
+ # This is safer than using backticks as it prevents shell injection.
27
+ # Arguments are automatically escaped using Shellwords.
28
+ #
29
+ # @param cmd [String] Command to execute
30
+ # @param args [Array<String>] Arguments (will be escaped)
31
+ # @param env [Hash] Environment variables to set
32
+ # @param chdir [String, nil] Working directory
33
+ # @param timeout [Integer] Timeout in seconds (default: 30)
34
+ # @return [Hash] Result with :success, :output, :error, :exitcode, :timeout keys
35
+ def self.run(cmd, *args, env: {}, chdir: nil, timeout: 30)
36
+ # Escape all arguments
37
+ escaped_args = args.map { |arg| Shellwords.escape(arg.to_s) }
38
+ full_cmd = "#{cmd} #{escaped_args.join(' ')}"
39
+
40
+ # Execute with timeout
41
+ stdout = nil
42
+ stderr = nil
43
+ status = nil
44
+
45
+ begin
46
+ Timeout.timeout(timeout) do
47
+ stdout, stderr, status = Open3.capture3(env, full_cmd, chdir: chdir)
48
+ end
49
+ rescue Timeout::Error
50
+ return {
51
+ success: false,
52
+ output: '',
53
+ error: "Command timed out after #{timeout} seconds",
54
+ exitcode: -1,
55
+ timeout: true
56
+ }
57
+ rescue StandardError => e
58
+ return {
59
+ success: false,
60
+ output: '',
61
+ error: e.message,
62
+ exitcode: -1
63
+ }
64
+ end
65
+
66
+ {
67
+ success: status.success?,
68
+ output: stdout,
69
+ error: stderr,
70
+ exitcode: status.exitstatus,
71
+ timeout: false
72
+ }
73
+ end
74
+
75
+ # Run a command and return only stdout (like backticks)
76
+ # Returns nil if the command fails
77
+ def self.capture(cmd, *, **)
78
+ result = run(cmd, *, **)
79
+ result[:success] ? result[:output] : nil
80
+ end
81
+
82
+ # Run a command and return stdout, raising on failure
83
+ def self.capture!(cmd, *, **)
84
+ result = run(cmd, *, **)
85
+ raise "Command failed (exit #{result[:exitcode]}): #{result[:error]}" unless result[:success]
86
+
87
+ result[:output]
88
+ end
89
+
90
+ # Check if a command exists in PATH
91
+ def self.command_exists?(cmd)
92
+ result = run('which', cmd)
93
+ result[:success]
94
+ end
95
+
96
+ # Run a command in the background and return immediately
97
+ # @deprecated This method has been removed for security reasons
98
+ # @raise [SecurityError] Always raises an error
99
+ def self.spawn(_cmd, *_args, env: {}, chdir: nil)
100
+ raise SecurityError, 'Shell.spawn has been removed for security reasons. Background process execution is not allowed in synthesized code.'
101
+ end
102
+
103
+ # Execute raw shell command (REMOVED FOR SECURITY)
104
+ # This method has been removed as it allowed arbitrary shell execution
105
+ # with pipes, redirects, etc. which is a security risk in synthesized code.
106
+ # @deprecated This method has been removed for security reasons
107
+ # @raise [SecurityError] Always raises an error
108
+ def self.raw(_command, env: {}, chdir: nil, timeout: 30)
109
+ raise SecurityError, 'Shell.raw has been removed for security reasons. Use Shell.run with explicit arguments instead.'
110
+ end
111
+
112
+ # Safely build a command string with escaped arguments
113
+ # Useful when you need to construct a command but not execute it yet
114
+ def self.build(cmd, *args)
115
+ escaped_args = args.map { |arg| Shellwords.escape(arg.to_s) }
116
+ "#{cmd} #{escaped_args.join(' ')}"
117
+ end
118
+
119
+ # Escape a single argument for shell usage
120
+ def self.escape(arg)
121
+ Shellwords.escape(arg.to_s)
122
+ end
123
+ end
124
+ end
125
+ end