language-operator 0.1.30 → 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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/Gemfile.lock +1 -1
  4. data/Makefile +7 -2
  5. data/Rakefile +29 -0
  6. data/docs/dsl/SCHEMA_VERSION.md +250 -0
  7. data/docs/dsl/agent-reference.md +13 -0
  8. data/lib/language_operator/agent/safety/safe_executor.rb +12 -0
  9. data/lib/language_operator/cli/commands/agent.rb +54 -101
  10. data/lib/language_operator/cli/commands/cluster.rb +37 -1
  11. data/lib/language_operator/cli/commands/persona.rb +2 -5
  12. data/lib/language_operator/cli/commands/status.rb +5 -18
  13. data/lib/language_operator/cli/commands/system.rb +772 -0
  14. data/lib/language_operator/cli/formatters/code_formatter.rb +3 -7
  15. data/lib/language_operator/cli/formatters/log_formatter.rb +3 -5
  16. data/lib/language_operator/cli/formatters/progress_formatter.rb +3 -7
  17. data/lib/language_operator/cli/formatters/status_formatter.rb +37 -0
  18. data/lib/language_operator/cli/formatters/table_formatter.rb +10 -26
  19. data/lib/language_operator/cli/helpers/pastel_helper.rb +24 -0
  20. data/lib/language_operator/cli/main.rb +4 -0
  21. data/lib/language_operator/dsl/schema.rb +1102 -0
  22. data/lib/language_operator/dsl.rb +1 -0
  23. data/lib/language_operator/logger.rb +4 -4
  24. data/lib/language_operator/templates/README.md +23 -0
  25. data/lib/language_operator/templates/examples/agent_synthesis.tmpl +115 -0
  26. data/lib/language_operator/templates/examples/persona_distillation.tmpl +19 -0
  27. data/lib/language_operator/templates/schema/.gitkeep +0 -0
  28. data/lib/language_operator/templates/schema/CHANGELOG.md +93 -0
  29. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +306 -0
  30. data/lib/language_operator/templates/schema/agent_dsl_schema.json +452 -0
  31. data/lib/language_operator/version.rb +1 -1
  32. data/requirements/tasks/iterate.md +2 -2
  33. metadata +13 -9
  34. data/examples/README.md +0 -569
  35. data/examples/agent_example.rb +0 -86
  36. data/examples/chat_endpoint_agent.rb +0 -118
  37. data/examples/github_webhook_agent.rb +0 -171
  38. data/examples/mcp_agent.rb +0 -158
  39. data/examples/oauth_callback_agent.rb +0 -296
  40. data/examples/stripe_webhook_agent.rb +0 -219
  41. data/examples/webhook_agent.rb +0 -80
@@ -1,296 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # OAuth Callback Agent Example
5
- #
6
- # This example shows how to create an agent that handles OAuth 2.0 callbacks
7
- # from various providers (GitHub, Google, etc.).
8
- #
9
- # Setup:
10
- # 1. Set OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET environment variables
11
- # 2. Configure OAuth provider redirect URI to https://<agent-url>/oauth/callback
12
- # 3. Set OAUTH_STATE_SECRET for state parameter verification
13
- #
14
- # OAuth 2.0 flow:
15
- # 1. User clicks "Login with Provider" -> redirected to provider
16
- # 2. User authorizes -> provider redirects to /oauth/callback with code
17
- # 3. Agent exchanges code for access token
18
- # 4. Agent fetches user info and creates session
19
-
20
- require 'bundler/setup'
21
- require 'language_operator'
22
- require 'net/http'
23
- require 'uri'
24
-
25
- LanguageOperator::Dsl.define_agents do
26
- agent 'oauth-handler' do
27
- description 'Handles OAuth callbacks and manages user authentication'
28
-
29
- # Set to reactive mode to receive callbacks
30
- mode :reactive
31
-
32
- # GitHub OAuth callback
33
- webhook '/oauth/github/callback' do
34
- method :get
35
-
36
- # Validate state parameter to prevent CSRF attacks
37
- validate do |context|
38
- state = context[:params]['state']
39
- return 'Missing state parameter' unless state
40
-
41
- # In production, verify state was generated by your app
42
- # and hasn't been used before (store in Redis/database)
43
- expected_state = ENV.fetch('OAUTH_STATE_SECRET', nil)
44
- return 'Invalid state parameter' unless state == expected_state
45
-
46
- true
47
- end
48
-
49
- on_request do |context|
50
- code = context[:params]['code']
51
- error = context[:params]['error']
52
-
53
- # Handle OAuth errors
54
- if error
55
- return {
56
- status: 'error',
57
- error: error,
58
- error_description: context[:params]['error_description'],
59
- message: 'OAuth authorization failed'
60
- }
61
- end
62
-
63
- # Exchange authorization code for access token
64
- token_response = exchange_code_for_token(
65
- code: code,
66
- client_id: ENV.fetch('GITHUB_CLIENT_ID', nil),
67
- client_secret: ENV.fetch('GITHUB_CLIENT_SECRET', nil),
68
- token_url: 'https://github.com/login/oauth/access_token'
69
- )
70
-
71
- if token_response[:error]
72
- return {
73
- status: 'error',
74
- error: token_response[:error],
75
- message: 'Failed to exchange code for token'
76
- }
77
- end
78
-
79
- access_token = token_response[:access_token]
80
-
81
- # Fetch user info from GitHub
82
- user_info = fetch_github_user(access_token)
83
-
84
- {
85
- status: 'success',
86
- message: 'OAuth authentication successful',
87
- user: {
88
- id: user_info['id'],
89
- username: user_info['login'],
90
- email: user_info['email'],
91
- name: user_info['name']
92
- },
93
- # In production, create session/JWT here
94
- session_created: true
95
- }
96
- end
97
- end
98
-
99
- # Google OAuth callback
100
- webhook '/oauth/google/callback' do
101
- method :get
102
-
103
- validate do |context|
104
- state = context[:params]['state']
105
- return 'Missing state parameter' unless state
106
-
107
- expected_state = ENV.fetch('OAUTH_STATE_SECRET', nil)
108
- return 'Invalid state parameter' unless state == expected_state
109
-
110
- true
111
- end
112
-
113
- on_request do |context|
114
- code = context[:params]['code']
115
- error = context[:params]['error']
116
-
117
- if error
118
- return {
119
- status: 'error',
120
- error: error,
121
- message: 'OAuth authorization failed'
122
- }
123
- end
124
-
125
- # Exchange code for token
126
- token_response = exchange_code_for_token(
127
- code: code,
128
- client_id: ENV.fetch('GOOGLE_CLIENT_ID', nil),
129
- client_secret: ENV.fetch('GOOGLE_CLIENT_SECRET', nil),
130
- token_url: 'https://oauth2.googleapis.com/token',
131
- redirect_uri: ENV.fetch('GOOGLE_REDIRECT_URI', nil)
132
- )
133
-
134
- if token_response[:error]
135
- return {
136
- status: 'error',
137
- error: token_response[:error],
138
- message: 'Failed to exchange code for token'
139
- }
140
- end
141
-
142
- access_token = token_response[:access_token]
143
- token_response[:id_token]
144
-
145
- # Fetch user info from Google
146
- user_info = fetch_google_user(access_token)
147
-
148
- {
149
- status: 'success',
150
- message: 'OAuth authentication successful',
151
- user: {
152
- id: user_info['id'],
153
- email: user_info['email'],
154
- name: user_info['name'],
155
- picture: user_info['picture']
156
- },
157
- session_created: true
158
- }
159
- end
160
- end
161
-
162
- # Generic OAuth callback with bearer token auth
163
- # (for testing or internal use)
164
- webhook '/oauth/test/callback' do
165
- method :post
166
-
167
- # Require bearer token for security
168
- authenticate do
169
- verify_bearer_token(token: ENV.fetch('OAUTH_TEST_TOKEN', nil))
170
- end
171
-
172
- require_content_type 'application/json'
173
-
174
- on_request do |context|
175
- data = JSON.parse(context[:body])
176
-
177
- {
178
- status: 'success',
179
- message: 'Test callback received',
180
- data: data
181
- }
182
- end
183
- end
184
-
185
- # OAuth initiation endpoint (starts the flow)
186
- webhook '/oauth/github/initiate' do
187
- method :get
188
-
189
- on_request do |_context|
190
- # Generate state parameter for CSRF protection
191
- SecureRandom.hex(32)
192
-
193
- # In production, store state in Redis/database with expiration
194
- # For demo purposes, we're just using a static value
195
-
196
- # Build GitHub authorization URL
197
- params = {
198
- client_id: ENV.fetch('GITHUB_CLIENT_ID', nil),
199
- redirect_uri: ENV.fetch('GITHUB_REDIRECT_URI', nil),
200
- scope: 'user:email',
201
- state: ENV.fetch('OAUTH_STATE_SECRET', nil) # Use proper state in production
202
- }
203
-
204
- auth_url = "https://github.com/login/oauth/authorize?#{URI.encode_www_form(params)}"
205
-
206
- # Return redirect response
207
- [
208
- 302,
209
- { 'Location' => auth_url },
210
- ['Redirecting to GitHub...']
211
- ]
212
- end
213
- end
214
-
215
- # Session verification endpoint
216
- webhook '/oauth/verify' do
217
- method :get
218
-
219
- # Verify bearer token
220
- authenticate do
221
- verify_bearer_token(token: ENV.fetch('SESSION_TOKEN', nil))
222
- end
223
-
224
- on_request do |_context|
225
- {
226
- status: 'valid',
227
- message: 'Session is valid',
228
- authenticated: true
229
- }
230
- end
231
- end
232
- end
233
- end
234
-
235
- # Helper methods
236
-
237
- def exchange_code_for_token(code:, client_id:, client_secret:, token_url:, redirect_uri: nil)
238
- uri = URI(token_url)
239
-
240
- params = {
241
- code: code,
242
- client_id: client_id,
243
- client_secret: client_secret,
244
- grant_type: 'authorization_code'
245
- }
246
- params[:redirect_uri] = redirect_uri if redirect_uri
247
-
248
- request = Net::HTTP::Post.new(uri)
249
- request.set_form_data(params)
250
- request['Accept'] = 'application/json'
251
-
252
- response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
253
- http.request(request)
254
- end
255
-
256
- JSON.parse(response.body, symbolize_names: true)
257
- rescue StandardError => e
258
- { error: e.message }
259
- end
260
-
261
- def fetch_github_user(access_token)
262
- uri = URI('https://api.github.com/user')
263
-
264
- request = Net::HTTP::Get.new(uri)
265
- request['Authorization'] = "Bearer #{access_token}"
266
- request['Accept'] = 'application/vnd.github.v3+json'
267
-
268
- response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
269
- http.request(request)
270
- end
271
-
272
- JSON.parse(response.body)
273
- rescue StandardError => e
274
- { error: e.message }
275
- end
276
-
277
- def fetch_google_user(access_token)
278
- uri = URI('https://www.googleapis.com/oauth2/v2/userinfo')
279
-
280
- request = Net::HTTP::Get.new(uri)
281
- request['Authorization'] = "Bearer #{access_token}"
282
-
283
- response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
284
- http.request(request)
285
- end
286
-
287
- JSON.parse(response.body)
288
- rescue StandardError => e
289
- { error: e.message }
290
- end
291
-
292
- # Run the agent if this file is executed directly
293
- if __FILE__ == $PROGRAM_NAME
294
- agent = LanguageOperator::Dsl.agent_registry.get('oauth-handler')
295
- agent.run!
296
- end
@@ -1,219 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Stripe Webhook Agent Example
5
- #
6
- # This example shows how to create an agent that receives Stripe webhook events
7
- # with proper signature verification for security.
8
- #
9
- # Setup:
10
- # 1. Set STRIPE_WEBHOOK_SECRET environment variable (from Stripe Dashboard)
11
- # 2. Configure Stripe webhook to send events to https://<agent-url>/stripe/events
12
- # 3. Select events you want to receive (e.g., payment_intent.succeeded, customer.created)
13
- #
14
- # Stripe webhook documentation:
15
- # https://stripe.com/docs/webhooks
16
-
17
- require 'bundler/setup'
18
- require 'language_operator'
19
-
20
- LanguageOperator::Dsl.define_agents do
21
- agent 'stripe-payment-processor' do
22
- description 'Processes Stripe payment events and triggers fulfillment'
23
-
24
- # Set to reactive mode to receive webhooks
25
- mode :reactive
26
-
27
- # Stripe webhook endpoint
28
- webhook '/stripe/events' do
29
- method :post
30
-
31
- # Verify Stripe webhook signature
32
- # Stripe sends signature in Stripe-Signature header
33
- # Format: "t=<timestamp>,v1=<signature>"
34
- # Note: For simplicity, we're verifying the v1 signature
35
- # Production should also check timestamp to prevent replay attacks
36
- authenticate do
37
- verify_custom do |context|
38
- signature_header = context[:headers]['Stripe-Signature']
39
- return false unless signature_header
40
-
41
- # Parse signature header
42
- # Format: t=1614556800,v1=abc123,v0=def456
43
- sig_parts = {}
44
- signature_header.split(',').each do |part|
45
- key, value = part.split('=', 2)
46
- sig_parts[key] = value
47
- end
48
-
49
- timestamp = sig_parts['t']
50
- signature = sig_parts['v1']
51
- return false unless timestamp && signature
52
-
53
- # Construct signed payload
54
- signed_payload = "#{timestamp}.#{context[:body]}"
55
-
56
- # Compute expected signature
57
- secret = ENV.fetch('STRIPE_WEBHOOK_SECRET', nil)
58
- expected = OpenSSL::HMAC.hexdigest('sha256', secret, signed_payload)
59
-
60
- # Compare signatures (constant-time)
61
- signature == expected
62
- end
63
- end
64
-
65
- # Validate request format
66
- require_content_type 'application/json'
67
-
68
- on_request do |context|
69
- event = JSON.parse(context[:body])
70
- event_type = event['type']
71
- event_data = event['data']['object']
72
-
73
- case event_type
74
- when 'payment_intent.succeeded'
75
- handle_payment_succeeded(event_data)
76
- when 'payment_intent.payment_failed'
77
- handle_payment_failed(event_data)
78
- when 'customer.created'
79
- handle_customer_created(event_data)
80
- when 'customer.subscription.created'
81
- handle_subscription_created(event_data)
82
- when 'customer.subscription.deleted'
83
- handle_subscription_deleted(event_data)
84
- when 'invoice.payment_succeeded'
85
- handle_invoice_paid(event_data)
86
- when 'invoice.payment_failed'
87
- handle_invoice_failed(event_data)
88
- when 'charge.refunded'
89
- handle_refund(event_data)
90
- else
91
- { status: 'ignored', event_type: event_type }
92
- end
93
- end
94
- end
95
-
96
- # Alternative endpoint with API key authentication
97
- # Useful for testing or internal triggers
98
- webhook '/stripe/manual-trigger' do
99
- method :post
100
-
101
- # Verify API key
102
- authenticate do
103
- verify_api_key(
104
- header: 'X-API-Key',
105
- key: ENV.fetch('STRIPE_INTERNAL_API_KEY', nil)
106
- )
107
- end
108
-
109
- require_content_type 'application/json'
110
-
111
- on_request do |context|
112
- data = JSON.parse(context[:body])
113
- action = data['action']
114
-
115
- {
116
- status: 'processed',
117
- action: action,
118
- message: "Manual trigger received: #{action}"
119
- }
120
- end
121
- end
122
- end
123
- end
124
-
125
- # Helper methods for event handling
126
-
127
- def handle_payment_succeeded(payment_intent)
128
- {
129
- status: 'processed',
130
- event: 'payment_succeeded',
131
- amount: payment_intent['amount'],
132
- currency: payment_intent['currency'],
133
- customer: payment_intent['customer'],
134
- payment_intent_id: payment_intent['id'],
135
- message: "Payment of #{payment_intent['amount']} #{payment_intent['currency']} succeeded"
136
- }
137
- end
138
-
139
- def handle_payment_failed(payment_intent)
140
- {
141
- status: 'processed',
142
- event: 'payment_failed',
143
- amount: payment_intent['amount'],
144
- currency: payment_intent['currency'],
145
- customer: payment_intent['customer'],
146
- error: payment_intent['last_payment_error']&.dig('message'),
147
- message: 'Payment failed'
148
- }
149
- end
150
-
151
- def handle_customer_created(customer)
152
- {
153
- status: 'processed',
154
- event: 'customer_created',
155
- customer_id: customer['id'],
156
- email: customer['email'],
157
- message: "New customer created: #{customer['email']}"
158
- }
159
- end
160
-
161
- def handle_subscription_created(subscription)
162
- {
163
- status: 'processed',
164
- event: 'subscription_created',
165
- subscription_id: subscription['id'],
166
- customer: subscription['customer'],
167
- plan: subscription['items']['data'].first['price']['id'],
168
- message: "New subscription created for customer #{subscription['customer']}"
169
- }
170
- end
171
-
172
- def handle_subscription_deleted(subscription)
173
- {
174
- status: 'processed',
175
- event: 'subscription_deleted',
176
- subscription_id: subscription['id'],
177
- customer: subscription['customer'],
178
- message: "Subscription deleted for customer #{subscription['customer']}"
179
- }
180
- end
181
-
182
- def handle_invoice_paid(invoice)
183
- {
184
- status: 'processed',
185
- event: 'invoice_paid',
186
- invoice_id: invoice['id'],
187
- amount: invoice['amount_paid'],
188
- customer: invoice['customer'],
189
- message: "Invoice #{invoice['number']} paid"
190
- }
191
- end
192
-
193
- def handle_invoice_failed(invoice)
194
- {
195
- status: 'processed',
196
- event: 'invoice_failed',
197
- invoice_id: invoice['id'],
198
- amount: invoice['amount_due'],
199
- customer: invoice['customer'],
200
- message: "Invoice #{invoice['number']} payment failed"
201
- }
202
- end
203
-
204
- def handle_refund(charge)
205
- {
206
- status: 'processed',
207
- event: 'refund',
208
- charge_id: charge['id'],
209
- amount_refunded: charge['amount_refunded'],
210
- customer: charge['customer'],
211
- message: "Charge #{charge['id']} refunded"
212
- }
213
- end
214
-
215
- # Run the agent if this file is executed directly
216
- if __FILE__ == $PROGRAM_NAME
217
- agent = LanguageOperator::Dsl.agent_registry.get('stripe-payment-processor')
218
- agent.run!
219
- end
@@ -1,80 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Example webhook agent that demonstrates the reactive/HTTP server capability
5
- #
6
- # Usage:
7
- # PORT=8080 ruby examples/webhook_agent.rb
8
- #
9
- # Test with curl:
10
- # curl -X POST http://localhost:8080/webhook -H "Content-Type: application/json" -d '{"event": "test"}'
11
- # curl http://localhost:8080/health
12
-
13
- require 'bundler/setup'
14
- require 'language_operator'
15
- require 'language_operator/dsl'
16
-
17
- # Define a webhook agent using the DSL
18
- # rubocop:disable Metrics/BlockLength
19
- LanguageOperator::Dsl.define do
20
- agent 'example-webhook-handler' do
21
- description 'Example agent that handles HTTP webhooks'
22
- mode :reactive
23
-
24
- # Health check endpoint (already provided by default)
25
- # GET /health
26
-
27
- # Custom webhook endpoint
28
- webhook '/webhook' do
29
- method :post
30
- on_request do |context|
31
- puts "\n=== Received Webhook ==="
32
- puts "Method: #{context[:method]}"
33
- puts "Path: #{context[:path]}"
34
- puts "Body: #{context[:body]}"
35
- puts "Params: #{context[:params].inspect}"
36
- puts "========================\n"
37
-
38
- {
39
- status: 'received',
40
- message: 'Webhook processed successfully',
41
- received_data: context[:params]
42
- }
43
- end
44
- end
45
-
46
- # GitHub-style webhook endpoint
47
- webhook '/github/pr' do
48
- method :post
49
- on_request do |_context|
50
- puts "\n=== GitHub PR Webhook ==="
51
- puts 'Simulating PR review...'
52
- puts "============================\n"
53
-
54
- {
55
- status: 'pr_reviewed',
56
- message: 'Pull request review queued'
57
- }
58
- end
59
- end
60
- end
61
- end
62
- # rubocop:enable Metrics/BlockLength
63
-
64
- # Get the agent definition and run it
65
- agent_def = LanguageOperator::Dsl.agent_registry.get('example-webhook-handler')
66
-
67
- if agent_def
68
- puts "Starting webhook agent on port #{ENV.fetch('PORT', '8080')}"
69
- puts 'Available endpoints:'
70
- puts ' GET /health - Health check'
71
- puts ' GET /ready - Readiness check'
72
- puts ' POST /webhook - Generic webhook'
73
- puts ' POST /github/pr - GitHub PR webhook'
74
- puts "\nPress Ctrl+C to stop\n\n"
75
-
76
- agent_def.run!
77
- else
78
- puts 'Error: Agent definition not found'
79
- exit 1
80
- end