legate 0.1.0
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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +345 -0
- data/bin/legate +13 -0
- data/examples/00_quickstart.rb +51 -0
- data/examples/01_simple_agent.rb +105 -0
- data/examples/02_multi_tool_agent.rb +140 -0
- data/examples/03_custom_tool.rb +93 -0
- data/examples/04_agent_instructions.rb +84 -0
- data/examples/05_state_and_sessions.rb +91 -0
- data/examples/06_callbacks.rb +186 -0
- data/examples/07_async_jobs.rb +112 -0
- data/examples/08_loop_agent.rb +197 -0
- data/examples/09_sequential_workflow.rb +40 -0
- data/examples/10_parallel_workflow.rb +34 -0
- data/examples/11_agent_delegation.rb +24 -0
- data/examples/12_http_client_tool.rb +156 -0
- data/examples/13_authentication.rb +220 -0
- data/examples/14_mcp_client.rb +154 -0
- data/examples/15_mcp_server.rb +79 -0
- data/examples/16_webhooks.rb +91 -0
- data/examples/README_sequential_agents.md +164 -0
- data/examples/advanced/auth/cookie_auth_tool.rb +146 -0
- data/examples/advanced/auth/custom_auth_flows_example.rb +626 -0
- data/examples/advanced/auth/excon_middleware.rb +317 -0
- data/examples/advanced/auth/excon_middleware_auth.rb +399 -0
- data/examples/advanced/auth/fiber_auth_example.rb +281 -0
- data/examples/advanced/auth/fiber_oidc_example.rb +403 -0
- data/examples/advanced/auth/httpbin_bearer_tool.rb +159 -0
- data/examples/advanced/auth/oauth2_auth.rb +419 -0
- data/examples/advanced/auth/oidc_auth.rb +514 -0
- data/examples/advanced/auth/openweather_api.rb +251 -0
- data/examples/advanced/auth/openweather_tool.rb +153 -0
- data/examples/advanced/auth/query_param_middleware_test.rb +138 -0
- data/examples/advanced/auth/service_account.rb +135 -0
- data/examples/advanced/auth/test_with_httpbin.rb +202 -0
- data/examples/advanced/auth/token_lifecycle_example.rb +428 -0
- data/examples/advanced/callback_monitoring.rb +679 -0
- data/examples/advanced/mas/fixed_delegation_example.rb +191 -0
- data/examples/advanced/mas/loop_workflow.rb +28 -0
- data/examples/advanced/mas/mock_planner.rb +77 -0
- data/examples/advanced/mas/proper_delegation_example.rb +276 -0
- data/examples/advanced/mcp/legate_mcp_server_resource_example.rb +182 -0
- data/examples/advanced/mcp/mcp_resource_server_example.rb +309 -0
- data/examples/advanced/mcp/mcp_server_async.rb +76 -0
- data/examples/advanced/mcp/mcp_server_async_tools.rb +122 -0
- data/examples/advanced/mcp/mcp_server_legate_agent.rb +95 -0
- data/examples/advanced/mcp/mcp_server_rack.rb +89 -0
- data/examples/advanced/random_calculator.rb +104 -0
- data/examples/advanced/sleep_agent.rb +153 -0
- data/examples/advanced/webhooks/webhook_e2e_runner.rb +110 -0
- data/examples/advanced/webhooks/webhook_receiver_agent.rb +58 -0
- data/examples/advanced/workflows/task_refinement_loop_agent.rb +278 -0
- data/examples/advanced/workflows/travel_planner_auto_sequential.rb +444 -0
- data/examples/advanced/workflows/travel_planner_parallel.rb +656 -0
- data/examples/advanced/workflows/travel_planner_sequential.rb +512 -0
- data/examples/tools/oauth2_example.rb +136 -0
- data/examples/tools/sleepy_tool.rb +42 -0
- data/lib/legate/activity_log.rb +71 -0
- data/lib/legate/agent.rb +959 -0
- data/lib/legate/agent_code_generator.rb +185 -0
- data/lib/legate/agent_definition.rb +812 -0
- data/lib/legate/agentic/decision.rb +49 -0
- data/lib/legate/agentic/loop.rb +134 -0
- data/lib/legate/agentic.rb +5 -0
- data/lib/legate/agents/loop_agent.rb +248 -0
- data/lib/legate/agents/parallel_agent.rb +163 -0
- data/lib/legate/agents/sequential_agent.rb +190 -0
- data/lib/legate/agents.rb +14 -0
- data/lib/legate/auth/config.rb +148 -0
- data/lib/legate/auth/coordinator.rb +218 -0
- data/lib/legate/auth/coordinators/oauth2_coordinator.rb +99 -0
- data/lib/legate/auth/coordinators/oidc_coordinator.rb +68 -0
- data/lib/legate/auth/coordinators/service_account_coordinator.rb +122 -0
- data/lib/legate/auth/credential.rb +157 -0
- data/lib/legate/auth/encryption.rb +108 -0
- data/lib/legate/auth/error.rb +94 -0
- data/lib/legate/auth/exchanged_credential.rb +180 -0
- data/lib/legate/auth/excon_middleware.rb +285 -0
- data/lib/legate/auth/http_client_utils.rb +364 -0
- data/lib/legate/auth/manager.rb +531 -0
- data/lib/legate/auth/manager_store.rb +394 -0
- data/lib/legate/auth/middleware_factory.rb +290 -0
- data/lib/legate/auth/runner.rb +279 -0
- data/lib/legate/auth/scheme.rb +125 -0
- data/lib/legate/auth/schemes/api_key.rb +212 -0
- data/lib/legate/auth/schemes/google_service_account.rb +108 -0
- data/lib/legate/auth/schemes/http_bearer.rb +98 -0
- data/lib/legate/auth/schemes/oauth2.rb +396 -0
- data/lib/legate/auth/schemes/openid_connect.rb +346 -0
- data/lib/legate/auth/schemes/service_account.rb +388 -0
- data/lib/legate/auth/schemes.rb +40 -0
- data/lib/legate/auth/token_manager.rb +362 -0
- data/lib/legate/auth/token_store.rb +86 -0
- data/lib/legate/auth/tool_context_extension.rb +97 -0
- data/lib/legate/auth/tool_integration.rb +188 -0
- data/lib/legate/auth/url_guard.rb +81 -0
- data/lib/legate/auth.rb +453 -0
- data/lib/legate/callbacks/callback_context.rb +71 -0
- data/lib/legate/cli/agent_commands.rb +950 -0
- data/lib/legate/cli/auth_commands.rb +520 -0
- data/lib/legate/cli/base_command.rb +24 -0
- data/lib/legate/cli/deployment_commands.rb +934 -0
- data/lib/legate/cli/output_helper.rb +108 -0
- data/lib/legate/cli/session_commands.rb +138 -0
- data/lib/legate/cli/skaffold_commands.rb +223 -0
- data/lib/legate/cli/tool_commands.rb +261 -0
- data/lib/legate/cli/web_commands.rb +182 -0
- data/lib/legate/cli.rb +40 -0
- data/lib/legate/configuration/webhooks.rb +113 -0
- data/lib/legate/configuration.rb +39 -0
- data/lib/legate/definition_store.rb +23 -0
- data/lib/legate/errors.rb +118 -0
- data/lib/legate/event.rb +161 -0
- data/lib/legate/gemini_ai_beta_patch.rb +39 -0
- data/lib/legate/generators/agent_generator.rb +412 -0
- data/lib/legate/generators/code_validator.rb +48 -0
- data/lib/legate/generators/legate/install_generator.rb +35 -0
- data/lib/legate/generators/legate/templates/create_legate_tables.rb.tt +36 -0
- data/lib/legate/generators/legate/templates/initializer.rb +18 -0
- data/lib/legate/generators/runtime_tool_loader.rb +76 -0
- data/lib/legate/generators/tool_generator.rb +408 -0
- data/lib/legate/generators.rb +11 -0
- data/lib/legate/global_definition_registry.rb +506 -0
- data/lib/legate/global_tool_manager.rb +135 -0
- data/lib/legate/llm/adapter.rb +69 -0
- data/lib/legate/llm/gemini.rb +172 -0
- data/lib/legate/llm/ollama.rb +80 -0
- data/lib/legate/llm.rb +34 -0
- data/lib/legate/mcp/client.rb +320 -0
- data/lib/legate/mcp/connection/sse.rb +292 -0
- data/lib/legate/mcp/connection/stdio.rb +273 -0
- data/lib/legate/mcp/connection_manager.rb +103 -0
- data/lib/legate/mcp/server/legate_agent_adapter.rb +170 -0
- data/lib/legate/mcp/server/legate_direct_agent_adapter.rb +140 -0
- data/lib/legate/mcp/server/legate_tool_adapter.rb +119 -0
- data/lib/legate/mcp/tool_wrapper.rb +138 -0
- data/lib/legate/mcp/util/schema_converter.rb +134 -0
- data/lib/legate/mcp.rb +23 -0
- data/lib/legate/plan_executor.rb +375 -0
- data/lib/legate/planner.rb +839 -0
- data/lib/legate/rails/railtie.rb +43 -0
- data/lib/legate/rails.rb +9 -0
- data/lib/legate/redaction.rb +32 -0
- data/lib/legate/session.rb +299 -0
- data/lib/legate/session_service/active_record.rb +300 -0
- data/lib/legate/session_service/base.rb +68 -0
- data/lib/legate/session_service/event_broadcast.rb +74 -0
- data/lib/legate/session_service/in_memory.rb +188 -0
- data/lib/legate/tool/metadata_dsl.rb +122 -0
- data/lib/legate/tool.rb +276 -0
- data/lib/legate/tool_code_generator.rb +103 -0
- data/lib/legate/tool_context.rb +350 -0
- data/lib/legate/tool_loader.rb +39 -0
- data/lib/legate/tool_registry.rb +73 -0
- data/lib/legate/tool_result.rb +61 -0
- data/lib/legate/tools/agent_tool.rb +187 -0
- data/lib/legate/tools/base/http_client.rb +319 -0
- data/lib/legate/tools/base/safe_url.rb +56 -0
- data/lib/legate/tools/base_async_job_tool.rb +91 -0
- data/lib/legate/tools/calculator.rb +89 -0
- data/lib/legate/tools/cat_facts.rb +81 -0
- data/lib/legate/tools/check_job_status_tool.rb +48 -0
- data/lib/legate/tools/current_time_tool.rb +64 -0
- data/lib/legate/tools/echo.rb +43 -0
- data/lib/legate/tools/http_request_tool.rb +105 -0
- data/lib/legate/tools/random_number_tool.rb +64 -0
- data/lib/legate/tools/read_webpage_tool.rb +92 -0
- data/lib/legate/tools/sleepy_tool.rb +74 -0
- data/lib/legate/tools/webhook_tool.rb +146 -0
- data/lib/legate/version.rb +5 -0
- data/lib/legate/web/app.rb +984 -0
- data/lib/legate/web/public/css/main.css +4980 -0
- data/lib/legate/web/public/images/favicon-256.png +0 -0
- data/lib/legate/web/public/images/favicon-32.png +0 -0
- data/lib/legate/web/public/images/legate-logo-dark.png +0 -0
- data/lib/legate/web/public/images/legate-logo-light.png +0 -0
- data/lib/legate/web/public/js/legate.js +616 -0
- data/lib/legate/web/public/styles/main.scss +4402 -0
- data/lib/legate/web/routes/agent_authentication_routes.rb +530 -0
- data/lib/legate/web/routes/agent_definition_routes.rb +803 -0
- data/lib/legate/web/routes/agent_generator_routes.rb +80 -0
- data/lib/legate/web/routes/agent_interaction_routes.rb +734 -0
- data/lib/legate/web/routes/agent_runtime_routes.rb +323 -0
- data/lib/legate/web/routes/api_routes.rb +56 -0
- data/lib/legate/web/routes/authentication_routes.rb +1541 -0
- data/lib/legate/web/routes/core_routes.rb +111 -0
- data/lib/legate/web/routes/documentation_routes.rb +220 -0
- data/lib/legate/web/routes/tool_generator_routes.rb +81 -0
- data/lib/legate/web/routes/tools_ui_routes.rb +207 -0
- data/lib/legate/web/sass_compiler.rb +73 -0
- data/lib/legate/web/views/_active_session_info.slim +25 -0
- data/lib/legate/web/views/_activity_list.slim +55 -0
- data/lib/legate/web/views/_agent_card.slim +56 -0
- data/lib/legate/web/views/_agent_generator_modal.slim +382 -0
- data/lib/legate/web/views/_agent_status_controls.slim +71 -0
- data/lib/legate/web/views/_agent_tool_table.slim +74 -0
- data/lib/legate/web/views/_chat_message.slim +95 -0
- data/lib/legate/web/views/_display_agent_configuration.slim +26 -0
- data/lib/legate/web/views/_display_agent_description.slim +11 -0
- data/lib/legate/web/views/_display_agent_fallback.slim +15 -0
- data/lib/legate/web/views/_display_agent_hierarchy.slim +93 -0
- data/lib/legate/web/views/_display_agent_instruction.slim +17 -0
- data/lib/legate/web/views/_display_agent_mcp.slim +13 -0
- data/lib/legate/web/views/_display_agent_model.slim +17 -0
- data/lib/legate/web/views/_display_agent_name.slim +42 -0
- data/lib/legate/web/views/_display_agent_output_key.slim +26 -0
- data/lib/legate/web/views/_display_agent_type.slim +65 -0
- data/lib/legate/web/views/_edit_agent_configuration.slim +74 -0
- data/lib/legate/web/views/_edit_agent_description.slim +16 -0
- data/lib/legate/web/views/_edit_agent_fallback.slim +25 -0
- data/lib/legate/web/views/_edit_agent_hierarchy.slim +98 -0
- data/lib/legate/web/views/_edit_agent_instruction.slim +49 -0
- data/lib/legate/web/views/_edit_agent_mcp.slim +33 -0
- data/lib/legate/web/views/_edit_agent_model.slim +23 -0
- data/lib/legate/web/views/_edit_agent_output_key.slim +36 -0
- data/lib/legate/web/views/_edit_agent_tools.slim +40 -0
- data/lib/legate/web/views/_edit_agent_type.slim +67 -0
- data/lib/legate/web/views/_session_error.slim +4 -0
- data/lib/legate/web/views/_skeleton.slim +69 -0
- data/lib/legate/web/views/_tool_card.slim +9 -0
- data/lib/legate/web/views/_tool_generator_modal.slim +311 -0
- data/lib/legate/web/views/agent.slim +436 -0
- data/lib/legate/web/views/agent_auth.slim +562 -0
- data/lib/legate/web/views/agents.slim +369 -0
- data/lib/legate/web/views/auth.slim +112 -0
- data/lib/legate/web/views/auth_credential_detail.slim +327 -0
- data/lib/legate/web/views/auth_credentials.slim +261 -0
- data/lib/legate/web/views/auth_debug.slim +94 -0
- data/lib/legate/web/views/auth_mapping_detail.slim +151 -0
- data/lib/legate/web/views/auth_mapping_new.slim +123 -0
- data/lib/legate/web/views/auth_mappings.slim +120 -0
- data/lib/legate/web/views/auth_scheme_detail.slim +274 -0
- data/lib/legate/web/views/auth_schemes.slim +259 -0
- data/lib/legate/web/views/auth_test.slim +418 -0
- data/lib/legate/web/views/chat.slim +192 -0
- data/lib/legate/web/views/docs_index.slim +105 -0
- data/lib/legate/web/views/docs_show.slim +105 -0
- data/lib/legate/web/views/error_404.slim +5 -0
- data/lib/legate/web/views/index.slim +148 -0
- data/lib/legate/web/views/layout.slim +144 -0
- data/lib/legate/web/views/tool_detail.slim +87 -0
- data/lib/legate/web/views/tools.slim +50 -0
- data/lib/legate/web/webhook_listener.rb +367 -0
- data/lib/legate/web.rb +9 -0
- data/lib/legate.rb +220 -0
- data/public/docs/advanced/callbacks.md +828 -0
- data/public/docs/advanced/mcp_schema_conversion.md +59 -0
- data/public/docs/authentication/api_reference/config.md +210 -0
- data/public/docs/authentication/api_reference/credential.md +246 -0
- data/public/docs/authentication/api_reference/encryption.md +218 -0
- data/public/docs/authentication/api_reference/exchanged_credential.md +271 -0
- data/public/docs/authentication/api_reference/excon_middleware.md +175 -0
- data/public/docs/authentication/api_reference/index.md +30 -0
- data/public/docs/authentication/api_reference/scheme.md +250 -0
- data/public/docs/authentication/api_reference/schemes/api_key.md +175 -0
- data/public/docs/authentication/api_reference/schemes/google_service_account.md +221 -0
- data/public/docs/authentication/api_reference/schemes/http_bearer.md +169 -0
- data/public/docs/authentication/api_reference/schemes/oauth2.md +343 -0
- data/public/docs/authentication/api_reference/schemes/oidc.md +73 -0
- data/public/docs/authentication/api_reference/schemes/openid_connect.md +311 -0
- data/public/docs/authentication/api_reference/schemes/service_account.md +287 -0
- data/public/docs/authentication/api_reference/token_manager.md +221 -0
- data/public/docs/authentication/api_reference/token_store.md +146 -0
- data/public/docs/authentication/api_reference/tool_context_extension.md +166 -0
- data/public/docs/authentication/guides/api_key.md +190 -0
- data/public/docs/authentication/guides/bearer.md +172 -0
- data/public/docs/authentication/guides/configuration.md +255 -0
- data/public/docs/authentication/guides/custom_flow.md +523 -0
- data/public/docs/authentication/guides/index.md +24 -0
- data/public/docs/authentication/guides/migration.md +435 -0
- data/public/docs/authentication/guides/oauth2.md +252 -0
- data/public/docs/authentication/guides/oidc.md +241 -0
- data/public/docs/authentication/guides/overview.md +155 -0
- data/public/docs/authentication/guides/secure_storage.md +301 -0
- data/public/docs/authentication/guides/service_account.md +228 -0
- data/public/docs/authentication/guides/token_lifecycle.md +295 -0
- data/public/docs/authentication/guides/web_ui_integration.md +504 -0
- data/public/docs/authentication/index.md +58 -0
- data/public/docs/authentication/troubleshooting/credential_storage.md +550 -0
- data/public/docs/authentication/troubleshooting/environment_variables.md +540 -0
- data/public/docs/authentication/troubleshooting/index.md +11 -0
- data/public/docs/authentication/troubleshooting/oauth2_issues.md +220 -0
- data/public/docs/authentication/troubleshooting/oidc_issues.md +412 -0
- data/public/docs/authentication/troubleshooting/token_refresh.md +338 -0
- data/public/docs/cli/legate_cli_usage.md +363 -0
- data/public/docs/core_concepts/legate_agent_lifecycle.md +124 -0
- data/public/docs/core_concepts/legate_architecture_overview.md +110 -0
- data/public/docs/core_concepts/legate_configuration.md +116 -0
- data/public/docs/core_concepts/legate_definition_store.md +102 -0
- data/public/docs/core_concepts/legate_planner.md +94 -0
- data/public/docs/core_concepts/legate_session_service.md +104 -0
- data/public/docs/error_handling/legate_error_handling.md +122 -0
- data/public/docs/examples.md +199 -0
- data/public/docs/getting_started.md +111 -0
- data/public/docs/guides/agentic_agents.md +137 -0
- data/public/docs/guides/ai_code_generators.md +437 -0
- data/public/docs/guides/auto_loading.md +326 -0
- data/public/docs/guides/configuring_agent_webhooks.md +219 -0
- data/public/docs/guides/http_client_usage.md +264 -0
- data/public/docs/guides/llm_providers.md +137 -0
- data/public/docs/guides/mcp_client_integration.md +232 -0
- data/public/docs/guides/mcp_server_exposure.md +206 -0
- data/public/docs/guides/rails_integration.md +128 -0
- data/public/docs/guides/sending_outbound_webhooks.md +227 -0
- data/public/docs/guides/streaming.md +112 -0
- data/public/docs/guides/webhooks.md +288 -0
- data/public/docs/introduction.md +51 -0
- data/public/docs/multi_agent_systems/advanced_features.md +57 -0
- data/public/docs/multi_agent_systems/agent_delegation.md +190 -0
- data/public/docs/multi_agent_systems/agent_hierarchy.md +49 -0
- data/public/docs/multi_agent_systems/state_management.md +47 -0
- data/public/docs/multi_agent_systems/workflow_agents.md +72 -0
- data/public/docs/tools/legate_built_in_tools.md +332 -0
- data/public/docs/tools/legate_tools_and_registry.md +263 -0
- data/public/docs/web_ui/legate_web_ui.md +137 -0
- metadata +823 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'legate'
|
|
6
|
+
require 'legate/auth'
|
|
7
|
+
require 'legate/auth/schemes/http_bearer'
|
|
8
|
+
require 'json'
|
|
9
|
+
|
|
10
|
+
# Example of using Legate::Auth with bearer token authentication against httpbin.org
|
|
11
|
+
# This example demonstrates how to use bearer tokens for authentication using the Legate tool framework.
|
|
12
|
+
|
|
13
|
+
module Legate
|
|
14
|
+
module Tools
|
|
15
|
+
class HttpbinBearer < Legate::Tool
|
|
16
|
+
include Legate::Tools::Base::HttpClient
|
|
17
|
+
|
|
18
|
+
# Tool metadata
|
|
19
|
+
tool_description 'Makes authenticated requests to httpbin.org using bearer token'
|
|
20
|
+
|
|
21
|
+
parameter :endpoint,
|
|
22
|
+
type: :string,
|
|
23
|
+
description: 'The httpbin endpoint to call (e.g., bearer, headers)',
|
|
24
|
+
required: true
|
|
25
|
+
|
|
26
|
+
def initialize(options = {})
|
|
27
|
+
super()
|
|
28
|
+
@auth_scheme = options[:auth_scheme]
|
|
29
|
+
@auth_credential = options[:auth_credential]
|
|
30
|
+
setup_http_client(
|
|
31
|
+
base_url: 'https://httpbin.org',
|
|
32
|
+
options: {
|
|
33
|
+
connect_timeout: 3,
|
|
34
|
+
read_timeout: 3,
|
|
35
|
+
write_timeout: 3
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def perform_execution(params, _context)
|
|
43
|
+
# Extract parameters
|
|
44
|
+
endpoint = params[:endpoint]
|
|
45
|
+
|
|
46
|
+
# Prepare request with authentication
|
|
47
|
+
request = {
|
|
48
|
+
method: :get,
|
|
49
|
+
path: "/#{endpoint}",
|
|
50
|
+
headers: {
|
|
51
|
+
'Accept' => 'application/json'
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Apply authentication if configured
|
|
56
|
+
request = @auth_scheme.apply_to_request(request, @auth_credential) if @auth_scheme && @auth_credential
|
|
57
|
+
|
|
58
|
+
# Make the request using HttpClient's helper with any auth headers
|
|
59
|
+
response = http_get(
|
|
60
|
+
request[:path],
|
|
61
|
+
headers: request[:headers]
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Parse and return the response
|
|
65
|
+
begin
|
|
66
|
+
data = JSON.parse(response.body)
|
|
67
|
+
case response.status
|
|
68
|
+
when 200
|
|
69
|
+
{
|
|
70
|
+
status: :success,
|
|
71
|
+
message: 'Successfully authenticated with bearer token',
|
|
72
|
+
result: data
|
|
73
|
+
}
|
|
74
|
+
when 401
|
|
75
|
+
{
|
|
76
|
+
status: :error,
|
|
77
|
+
message: 'Authentication failed. Invalid or missing bearer token.',
|
|
78
|
+
error: data['message'] || 'Unauthorized'
|
|
79
|
+
}
|
|
80
|
+
else
|
|
81
|
+
{
|
|
82
|
+
status: :error,
|
|
83
|
+
message: "Request failed with status #{response.status}",
|
|
84
|
+
error: data
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
rescue JSON::ParserError => e
|
|
88
|
+
raise Legate::ToolError, "Failed to parse API response: #{e.message}"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
if $0 == __FILE__
|
|
96
|
+
puts 'Bearer Token Authentication Example'
|
|
97
|
+
puts '--------------------------------'
|
|
98
|
+
|
|
99
|
+
begin
|
|
100
|
+
# Create session service for token storage
|
|
101
|
+
session_service = Legate::SessionService::InMemory.new
|
|
102
|
+
token_store = Legate::Auth::TokenStore.new(session_service)
|
|
103
|
+
|
|
104
|
+
# Create a bearer token scheme
|
|
105
|
+
scheme = Legate::Auth::Schemes::HTTPBearer.new
|
|
106
|
+
|
|
107
|
+
# Create a credential with the bearer token
|
|
108
|
+
# In a real application, you would get this from an environment variable
|
|
109
|
+
credential = Legate::Auth::Credential.new(
|
|
110
|
+
auth_type: :http_bearer,
|
|
111
|
+
bearer_token: 'test-bearer-token'
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Create our HttpbinBearer tool instance with authentication
|
|
115
|
+
bearer_tool = Legate::Tools::HttpbinBearer.new(
|
|
116
|
+
auth_scheme: scheme,
|
|
117
|
+
auth_credential: credential
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Example endpoints to test
|
|
121
|
+
endpoints = %w[bearer headers]
|
|
122
|
+
|
|
123
|
+
# Test each endpoint
|
|
124
|
+
endpoints.each do |endpoint|
|
|
125
|
+
puts "\nTesting endpoint: /#{endpoint}"
|
|
126
|
+
|
|
127
|
+
begin
|
|
128
|
+
result = bearer_tool.execute(
|
|
129
|
+
endpoint: endpoint
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if result[:status] == :success
|
|
133
|
+
response_data = result[:result]
|
|
134
|
+
puts "Success: #{result[:message]}"
|
|
135
|
+
|
|
136
|
+
case endpoint
|
|
137
|
+
when 'bearer'
|
|
138
|
+
puts 'Bearer auth response:'
|
|
139
|
+
puts JSON.pretty_generate(response_data)
|
|
140
|
+
when 'headers'
|
|
141
|
+
puts 'Headers in request:'
|
|
142
|
+
puts JSON.pretty_generate(response_data['headers'])
|
|
143
|
+
end
|
|
144
|
+
else
|
|
145
|
+
puts "Error: #{result[:message]}"
|
|
146
|
+
puts "Details: #{result[:error]}"
|
|
147
|
+
end
|
|
148
|
+
rescue StandardError => e
|
|
149
|
+
puts "Error testing #{endpoint}: #{e.message}"
|
|
150
|
+
puts e.backtrace.join("\n") if ENV['DEBUG']
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
rescue StandardError => e
|
|
154
|
+
puts "Error: #{e.message}"
|
|
155
|
+
puts e.backtrace.join("\n") if ENV['DEBUG']
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
puts "\nExample complete."
|
|
159
|
+
end
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example of using OAuth2 authentication with Legate
|
|
5
|
+
#
|
|
6
|
+
# This example demonstrates how to use OAuth2 authentication to make authenticated requests
|
|
7
|
+
# to an API that requires OAuth2 authorization. It shows both the interactive and non-interactive
|
|
8
|
+
# (with refresh token) approaches.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# ruby examples/advanced/auth/oauth2_auth.rb [--with-server]
|
|
12
|
+
# ruby examples/advanced/auth/oauth2_auth.rb --client-id=YOUR_CLIENT_ID --client-secret=YOUR_CLIENT_SECRET
|
|
13
|
+
#
|
|
14
|
+
# Set these environment variables to use your own OAuth2 provider:
|
|
15
|
+
# OAUTH_CLIENT_ID
|
|
16
|
+
# OAUTH_CLIENT_SECRET
|
|
17
|
+
# OAUTH_AUTH_URL (default: https://accounts.google.com/o/oauth2/auth)
|
|
18
|
+
# OAUTH_TOKEN_URL (default: https://oauth2.googleapis.com/token)
|
|
19
|
+
# OAUTH_REDIRECT_URI (default: http://localhost:3000/auth/callback)
|
|
20
|
+
|
|
21
|
+
require 'bundler/setup'
|
|
22
|
+
require 'legate'
|
|
23
|
+
require 'legate/auth'
|
|
24
|
+
require 'legate/auth/runner'
|
|
25
|
+
require 'legate/auth/schemes/oauth2'
|
|
26
|
+
require 'legate/tool_context'
|
|
27
|
+
require 'legate/web/server'
|
|
28
|
+
require 'launchy'
|
|
29
|
+
require 'securerandom'
|
|
30
|
+
require 'optparse'
|
|
31
|
+
require 'json'
|
|
32
|
+
require 'fileutils'
|
|
33
|
+
|
|
34
|
+
# Parse command line arguments
|
|
35
|
+
options = {
|
|
36
|
+
with_server: false,
|
|
37
|
+
client_id: ENV['OAUTH_CLIENT_ID'],
|
|
38
|
+
client_secret: ENV['OAUTH_CLIENT_SECRET'],
|
|
39
|
+
auth_url: ENV['OAUTH_AUTH_URL'] || 'https://accounts.google.com/o/oauth2/auth',
|
|
40
|
+
token_url: ENV['OAUTH_TOKEN_URL'] || 'https://oauth2.googleapis.com/token',
|
|
41
|
+
redirect_uri: ENV['OAUTH_REDIRECT_URI'] || 'http://localhost:3000/auth/callback',
|
|
42
|
+
scopes: ENV['OAUTH_SCOPES'] || 'email profile',
|
|
43
|
+
handle_response: false,
|
|
44
|
+
request_id: nil,
|
|
45
|
+
response_uri: nil,
|
|
46
|
+
use_refresh_token: false,
|
|
47
|
+
verbose: false
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
OptionParser.new do |opts|
|
|
51
|
+
opts.banner = 'Usage: ruby examples/advanced/auth/oauth2_auth.rb [options]'
|
|
52
|
+
|
|
53
|
+
opts.on('--with-server', 'Start a local server to handle the OAuth2 callback') do
|
|
54
|
+
options[:with_server] = true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
opts.on('--client-id=ID', 'OAuth2 client ID') do |id|
|
|
58
|
+
options[:client_id] = id
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
opts.on('--client-secret=SECRET', 'OAuth2 client secret') do |secret|
|
|
62
|
+
options[:client_secret] = secret
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
opts.on('--auth-url=URL', 'OAuth2 authorization URL') do |url|
|
|
66
|
+
options[:auth_url] = url
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
opts.on('--token-url=URL', 'OAuth2 token URL') do |url|
|
|
70
|
+
options[:token_url] = url
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
opts.on('--redirect-uri=URI', 'OAuth2 redirect URI') do |uri|
|
|
74
|
+
options[:redirect_uri] = uri
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
opts.on('--scopes=SCOPES', 'OAuth2 scopes (space-separated)') do |scopes|
|
|
78
|
+
options[:scopes] = scopes
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
opts.on('--handle-response', 'Handle an authentication response') do
|
|
82
|
+
options[:handle_response] = true
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
opts.on('--request-id=ID', 'Authentication request ID') do |id|
|
|
86
|
+
options[:request_id] = id
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
opts.on('--response-uri=URI', 'Authentication response URI') do |uri|
|
|
90
|
+
options[:response_uri] = uri
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
opts.on('--use-refresh-token', 'Use a saved refresh token if available') do
|
|
94
|
+
options[:use_refresh_token] = true
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
opts.on('--verbose', 'Enable verbose output') do
|
|
98
|
+
options[:verbose] = true
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
opts.on('--help', 'Show this help message') do
|
|
102
|
+
puts opts
|
|
103
|
+
exit
|
|
104
|
+
end
|
|
105
|
+
end.parse!
|
|
106
|
+
|
|
107
|
+
# Validate required parameters
|
|
108
|
+
if options[:client_id].nil? || options[:client_id].empty?
|
|
109
|
+
puts 'Error: OAuth2 client ID is required'
|
|
110
|
+
puts 'Please provide it via --client-id or OAUTH_CLIENT_ID environment variable'
|
|
111
|
+
exit 1
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
if options[:client_secret].nil? || options[:client_secret].empty?
|
|
115
|
+
puts 'Error: OAuth2 client secret is required'
|
|
116
|
+
puts 'Please provide it via --client-secret or OAUTH_CLIENT_SECRET environment variable'
|
|
117
|
+
exit 1
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Setup the session service
|
|
121
|
+
session_service = Legate::SessionService::InMemory.new
|
|
122
|
+
|
|
123
|
+
# Create a tool context with the session service
|
|
124
|
+
# In a real application, you would use Redis or another persistent storage
|
|
125
|
+
context = Legate::ToolContext.new(session_service: session_service)
|
|
126
|
+
|
|
127
|
+
# Create an OAuth2 scheme
|
|
128
|
+
oauth2_scheme = Legate::Auth::Schemes::OAuth2.new(
|
|
129
|
+
authorization_url: options[:auth_url],
|
|
130
|
+
token_url: options[:token_url],
|
|
131
|
+
scopes: options[:scopes].split(' '),
|
|
132
|
+
use_pkce: true,
|
|
133
|
+
additional_params: {
|
|
134
|
+
prompt: 'consent',
|
|
135
|
+
access_type: 'offline'
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Create the OAuth2 credential
|
|
140
|
+
credential = Legate::Auth::Credential.new(
|
|
141
|
+
auth_type: :oauth2,
|
|
142
|
+
client_id: options[:client_id],
|
|
143
|
+
client_secret: options[:client_secret]
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Token storage file for refresh tokens
|
|
147
|
+
token_storage_file = File.expand_path('~/.oauth2_example_token.json')
|
|
148
|
+
|
|
149
|
+
# Helper to save token to file
|
|
150
|
+
def save_token_to_file(token, file_path)
|
|
151
|
+
# Convert token to a hash for storage
|
|
152
|
+
token_data = {
|
|
153
|
+
access_token: token.access_token,
|
|
154
|
+
refresh_token: token.refresh_token,
|
|
155
|
+
token_type: token.token_type,
|
|
156
|
+
expires_at: token.expires_at.to_i,
|
|
157
|
+
scope: token.scope
|
|
158
|
+
}.compact
|
|
159
|
+
|
|
160
|
+
# Save to file
|
|
161
|
+
FileUtils.mkdir_p(File.dirname(file_path))
|
|
162
|
+
File.write(file_path, JSON.pretty_generate(token_data))
|
|
163
|
+
puts "Token saved to #{file_path}"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Helper to load token from file
|
|
167
|
+
def load_token_from_file(file_path)
|
|
168
|
+
return nil unless File.exist?(file_path)
|
|
169
|
+
|
|
170
|
+
begin
|
|
171
|
+
token_data = JSON.parse(File.read(file_path), symbolize_names: true)
|
|
172
|
+
|
|
173
|
+
# Create an exchanged credential from the stored data
|
|
174
|
+
Legate::Auth::ExchangedCredential.new(
|
|
175
|
+
auth_type: :oauth2,
|
|
176
|
+
access_token: token_data[:access_token],
|
|
177
|
+
refresh_token: token_data[:refresh_token],
|
|
178
|
+
token_type: token_data[:token_type],
|
|
179
|
+
expires_at: Time.at(token_data[:expires_at]),
|
|
180
|
+
scope: token_data[:scope]
|
|
181
|
+
)
|
|
182
|
+
rescue StandardError => e
|
|
183
|
+
puts "Error loading token: #{e.message}"
|
|
184
|
+
nil
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Create a web server to handle the OAuth2 callback
|
|
189
|
+
def create_callback_server(context, request_id_holder)
|
|
190
|
+
server = Legate::Web::Server.new(port: 3000)
|
|
191
|
+
|
|
192
|
+
# Add a route to handle the OAuth2 callback
|
|
193
|
+
server.add_route('GET', '/auth/callback') do |req, res|
|
|
194
|
+
# Extract the authorization code from the query parameters
|
|
195
|
+
code = req.query['code']
|
|
196
|
+
state = req.query['state']
|
|
197
|
+
error = req.query['error']
|
|
198
|
+
|
|
199
|
+
if error
|
|
200
|
+
res.status = 400
|
|
201
|
+
res.body = "Authentication failed: #{error}"
|
|
202
|
+
elsif code
|
|
203
|
+
# Convert the request to a response URI
|
|
204
|
+
response_uri = "http://localhost:3000/auth/callback?#{req.query_string}"
|
|
205
|
+
|
|
206
|
+
# Find the active auth request
|
|
207
|
+
request_id = request_id_holder[:id] || nil
|
|
208
|
+
|
|
209
|
+
if request_id
|
|
210
|
+
# Handle the auth response
|
|
211
|
+
result = context.handle_auth_response(request_id, { 'response_uri' => response_uri })
|
|
212
|
+
|
|
213
|
+
# Show a success page
|
|
214
|
+
res.status = 200
|
|
215
|
+
res.body = <<~HTML
|
|
216
|
+
<!DOCTYPE html>
|
|
217
|
+
<html>
|
|
218
|
+
<head>
|
|
219
|
+
<title>Authentication Successful</title>
|
|
220
|
+
<style>
|
|
221
|
+
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
222
|
+
h1 { color: #4CAF50; }
|
|
223
|
+
.box { border: 1px solid #ddd; border-radius: 5px; padding: 20px; margin-top: 20px; }
|
|
224
|
+
</style>
|
|
225
|
+
</head>
|
|
226
|
+
<body>
|
|
227
|
+
<h1>Authentication Successful</h1>
|
|
228
|
+
<p>You have successfully authenticated with the OAuth2 provider.</p>
|
|
229
|
+
<div class="box">
|
|
230
|
+
<p>You can close this window and return to the application.</p>
|
|
231
|
+
</div>
|
|
232
|
+
</body>
|
|
233
|
+
</html>
|
|
234
|
+
HTML
|
|
235
|
+
else
|
|
236
|
+
res.status = 400
|
|
237
|
+
res.body = 'No active authentication request found'
|
|
238
|
+
end
|
|
239
|
+
else
|
|
240
|
+
res.status = 400
|
|
241
|
+
res.body = 'Missing required parameters'
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
server
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def make_authenticated_request(token)
|
|
249
|
+
# In a real application, you would make an API call to an OAuth2-protected endpoint
|
|
250
|
+
# This is just a simulation to show the token information
|
|
251
|
+
puts "\nMaking an authenticated request with token:"
|
|
252
|
+
puts " Access Token : #{token.access_token[0..9]}...#{token.access_token[-4..-1]}"
|
|
253
|
+
puts " Token Type : #{token.token_type}"
|
|
254
|
+
puts " Expires In : #{(token.expires_at - Time.now).to_i} seconds"
|
|
255
|
+
puts " Scopes : #{token.scope}"
|
|
256
|
+
|
|
257
|
+
# Print refresh token if available
|
|
258
|
+
puts " Refresh Token: #{token.refresh_token[0..5]}...#{token.refresh_token[-4..-1]}" if token.refresh_token
|
|
259
|
+
|
|
260
|
+
puts "\nAuthenticated request successful!"
|
|
261
|
+
|
|
262
|
+
{
|
|
263
|
+
status: 'success',
|
|
264
|
+
authenticated: true,
|
|
265
|
+
auth_method: 'OAuth2',
|
|
266
|
+
token_expiry: token.expires_at,
|
|
267
|
+
scopes: token.scope
|
|
268
|
+
}
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Main execution logic
|
|
272
|
+
begin
|
|
273
|
+
# Try to use saved refresh token
|
|
274
|
+
saved_token = nil
|
|
275
|
+
if options[:use_refresh_token] && File.exist?(token_storage_file)
|
|
276
|
+
puts 'Found saved token, attempting to use it...'
|
|
277
|
+
saved_token = load_token_from_file(token_storage_file)
|
|
278
|
+
|
|
279
|
+
if saved_token
|
|
280
|
+
if saved_token.expired?
|
|
281
|
+
puts 'Saved token is expired, attempting to refresh it...'
|
|
282
|
+
|
|
283
|
+
# Create a token store
|
|
284
|
+
token_store = Legate::Auth::TokenStore.new(session_service: session_service)
|
|
285
|
+
|
|
286
|
+
# Create a token manager
|
|
287
|
+
token_manager = Legate::Auth::TokenManager.new(token_store)
|
|
288
|
+
|
|
289
|
+
# Refresh the token
|
|
290
|
+
begin
|
|
291
|
+
refreshed_token = token_manager.refresh_token(oauth2_scheme, credential, saved_token)
|
|
292
|
+
puts 'Token refreshed successfully!'
|
|
293
|
+
|
|
294
|
+
# Save the refreshed token
|
|
295
|
+
save_token_to_file(refreshed_token, token_storage_file)
|
|
296
|
+
|
|
297
|
+
# Make a request with the refreshed token
|
|
298
|
+
result = make_authenticated_request(refreshed_token)
|
|
299
|
+
puts "\nAuthentication via refresh token successful!"
|
|
300
|
+
exit 0
|
|
301
|
+
rescue StandardError => e
|
|
302
|
+
puts "Token refresh failed: #{e.message}"
|
|
303
|
+
puts 'Will proceed with interactive authentication...'
|
|
304
|
+
# Continue with interactive flow below
|
|
305
|
+
end
|
|
306
|
+
else
|
|
307
|
+
puts 'Saved token is still valid, using it...'
|
|
308
|
+
result = make_authenticated_request(saved_token)
|
|
309
|
+
puts "\nAuthentication with saved token successful!"
|
|
310
|
+
exit 0
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Handle authentication response if requested
|
|
316
|
+
if options[:handle_response]
|
|
317
|
+
unless options[:request_id] && options[:response_uri]
|
|
318
|
+
puts 'Error: Both --request-id and --response-uri are required with --handle-response'
|
|
319
|
+
exit 1
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
puts "Handling authentication response for request ID: #{options[:request_id]}"
|
|
323
|
+
|
|
324
|
+
# Create a response object
|
|
325
|
+
response = { 'response_uri' => options[:response_uri] }
|
|
326
|
+
|
|
327
|
+
# Handle the authentication response
|
|
328
|
+
result = context.handle_auth_response(options[:request_id], response)
|
|
329
|
+
|
|
330
|
+
if result[:status] == :completed
|
|
331
|
+
puts 'Authentication completed successfully!'
|
|
332
|
+
token = result[:credential]
|
|
333
|
+
|
|
334
|
+
# Save the token for future use
|
|
335
|
+
save_token_to_file(token, token_storage_file)
|
|
336
|
+
|
|
337
|
+
# Make a request with the token
|
|
338
|
+
make_authenticated_request(token)
|
|
339
|
+
else
|
|
340
|
+
puts 'Authentication not completed:'
|
|
341
|
+
puts result.inspect
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
exit 0
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Start server if requested
|
|
348
|
+
if options[:with_server]
|
|
349
|
+
puts 'Starting local server to handle OAuth2 callback...'
|
|
350
|
+
request_id_holder = { id: nil }
|
|
351
|
+
server = create_callback_server(context, request_id_holder)
|
|
352
|
+
|
|
353
|
+
begin
|
|
354
|
+
# Start the server
|
|
355
|
+
server_thread = Thread.new { server.start }
|
|
356
|
+
|
|
357
|
+
# Use with_authentication to create a fiber context with auth support
|
|
358
|
+
result = context.with_authentication do
|
|
359
|
+
# This callback will be called when an authentication request is yielded
|
|
360
|
+
|
|
361
|
+
# Authenticate with the scheme and credential
|
|
362
|
+
token = context.auth_session(
|
|
363
|
+
oauth2_scheme,
|
|
364
|
+
credential,
|
|
365
|
+
redirect_uri: options[:redirect_uri]
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
if token
|
|
369
|
+
# Save the token for future use
|
|
370
|
+
save_token_to_file(token, token_storage_file)
|
|
371
|
+
|
|
372
|
+
# Make authenticated request
|
|
373
|
+
make_authenticated_request(token)
|
|
374
|
+
else
|
|
375
|
+
puts 'Failed to get authentication token'
|
|
376
|
+
{ status: :error, message: 'Authentication failed' }
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
puts "Result: #{result.inspect}"
|
|
381
|
+
ensure
|
|
382
|
+
# Stop the server
|
|
383
|
+
server.stop
|
|
384
|
+
# Wait for server thread to finish
|
|
385
|
+
server_thread.join if server_thread
|
|
386
|
+
end
|
|
387
|
+
else
|
|
388
|
+
# Manual flow without a local server
|
|
389
|
+
puts 'Starting OAuth2 authentication process...'
|
|
390
|
+
|
|
391
|
+
# Create an auth runner
|
|
392
|
+
auth_runner = Legate::Auth::Runner.new(session_service: session_service)
|
|
393
|
+
|
|
394
|
+
# Start authentication
|
|
395
|
+
auth_result = auth_runner.authenticate(oauth2_scheme, credential,
|
|
396
|
+
redirect_uri: options[:redirect_uri])
|
|
397
|
+
|
|
398
|
+
# Check if we got a token immediately (unlikely for OAuth2)
|
|
399
|
+
if auth_result.is_a?(Legate::Auth::ExchangedCredential)
|
|
400
|
+
puts 'Received token immediately (unusual for OAuth2):'
|
|
401
|
+
make_authenticated_request(auth_result)
|
|
402
|
+
else
|
|
403
|
+
# We should have an authentication request
|
|
404
|
+
request_id = auth_result[:request_id]
|
|
405
|
+
auth_request = auth_result[:auth_request]
|
|
406
|
+
|
|
407
|
+
puts 'Authentication required. Please visit this URL to authorize:'
|
|
408
|
+
puts " #{auth_request[:url]}"
|
|
409
|
+
puts
|
|
410
|
+
puts 'After authorization, you will be redirected. Copy the entire URL from your browser'
|
|
411
|
+
puts 'and run this command to complete authentication:'
|
|
412
|
+
puts
|
|
413
|
+
puts " ruby #{__FILE__} --handle-response --request-id #{request_id} --response-uri 'PASTE_URL_HERE'"
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
rescue StandardError => e
|
|
417
|
+
puts "Error: #{e.class}: #{e.message}"
|
|
418
|
+
puts e.backtrace.join("\n") if options[:verbose]
|
|
419
|
+
end
|