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.
Files changed (317) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +345 -0
  4. data/bin/legate +13 -0
  5. data/examples/00_quickstart.rb +51 -0
  6. data/examples/01_simple_agent.rb +105 -0
  7. data/examples/02_multi_tool_agent.rb +140 -0
  8. data/examples/03_custom_tool.rb +93 -0
  9. data/examples/04_agent_instructions.rb +84 -0
  10. data/examples/05_state_and_sessions.rb +91 -0
  11. data/examples/06_callbacks.rb +186 -0
  12. data/examples/07_async_jobs.rb +112 -0
  13. data/examples/08_loop_agent.rb +197 -0
  14. data/examples/09_sequential_workflow.rb +40 -0
  15. data/examples/10_parallel_workflow.rb +34 -0
  16. data/examples/11_agent_delegation.rb +24 -0
  17. data/examples/12_http_client_tool.rb +156 -0
  18. data/examples/13_authentication.rb +220 -0
  19. data/examples/14_mcp_client.rb +154 -0
  20. data/examples/15_mcp_server.rb +79 -0
  21. data/examples/16_webhooks.rb +91 -0
  22. data/examples/README_sequential_agents.md +164 -0
  23. data/examples/advanced/auth/cookie_auth_tool.rb +146 -0
  24. data/examples/advanced/auth/custom_auth_flows_example.rb +626 -0
  25. data/examples/advanced/auth/excon_middleware.rb +317 -0
  26. data/examples/advanced/auth/excon_middleware_auth.rb +399 -0
  27. data/examples/advanced/auth/fiber_auth_example.rb +281 -0
  28. data/examples/advanced/auth/fiber_oidc_example.rb +403 -0
  29. data/examples/advanced/auth/httpbin_bearer_tool.rb +159 -0
  30. data/examples/advanced/auth/oauth2_auth.rb +419 -0
  31. data/examples/advanced/auth/oidc_auth.rb +514 -0
  32. data/examples/advanced/auth/openweather_api.rb +251 -0
  33. data/examples/advanced/auth/openweather_tool.rb +153 -0
  34. data/examples/advanced/auth/query_param_middleware_test.rb +138 -0
  35. data/examples/advanced/auth/service_account.rb +135 -0
  36. data/examples/advanced/auth/test_with_httpbin.rb +202 -0
  37. data/examples/advanced/auth/token_lifecycle_example.rb +428 -0
  38. data/examples/advanced/callback_monitoring.rb +679 -0
  39. data/examples/advanced/mas/fixed_delegation_example.rb +191 -0
  40. data/examples/advanced/mas/loop_workflow.rb +28 -0
  41. data/examples/advanced/mas/mock_planner.rb +77 -0
  42. data/examples/advanced/mas/proper_delegation_example.rb +276 -0
  43. data/examples/advanced/mcp/legate_mcp_server_resource_example.rb +182 -0
  44. data/examples/advanced/mcp/mcp_resource_server_example.rb +309 -0
  45. data/examples/advanced/mcp/mcp_server_async.rb +76 -0
  46. data/examples/advanced/mcp/mcp_server_async_tools.rb +122 -0
  47. data/examples/advanced/mcp/mcp_server_legate_agent.rb +95 -0
  48. data/examples/advanced/mcp/mcp_server_rack.rb +89 -0
  49. data/examples/advanced/random_calculator.rb +104 -0
  50. data/examples/advanced/sleep_agent.rb +153 -0
  51. data/examples/advanced/webhooks/webhook_e2e_runner.rb +110 -0
  52. data/examples/advanced/webhooks/webhook_receiver_agent.rb +58 -0
  53. data/examples/advanced/workflows/task_refinement_loop_agent.rb +278 -0
  54. data/examples/advanced/workflows/travel_planner_auto_sequential.rb +444 -0
  55. data/examples/advanced/workflows/travel_planner_parallel.rb +656 -0
  56. data/examples/advanced/workflows/travel_planner_sequential.rb +512 -0
  57. data/examples/tools/oauth2_example.rb +136 -0
  58. data/examples/tools/sleepy_tool.rb +42 -0
  59. data/lib/legate/activity_log.rb +71 -0
  60. data/lib/legate/agent.rb +959 -0
  61. data/lib/legate/agent_code_generator.rb +185 -0
  62. data/lib/legate/agent_definition.rb +812 -0
  63. data/lib/legate/agentic/decision.rb +49 -0
  64. data/lib/legate/agentic/loop.rb +134 -0
  65. data/lib/legate/agentic.rb +5 -0
  66. data/lib/legate/agents/loop_agent.rb +248 -0
  67. data/lib/legate/agents/parallel_agent.rb +163 -0
  68. data/lib/legate/agents/sequential_agent.rb +190 -0
  69. data/lib/legate/agents.rb +14 -0
  70. data/lib/legate/auth/config.rb +148 -0
  71. data/lib/legate/auth/coordinator.rb +218 -0
  72. data/lib/legate/auth/coordinators/oauth2_coordinator.rb +99 -0
  73. data/lib/legate/auth/coordinators/oidc_coordinator.rb +68 -0
  74. data/lib/legate/auth/coordinators/service_account_coordinator.rb +122 -0
  75. data/lib/legate/auth/credential.rb +157 -0
  76. data/lib/legate/auth/encryption.rb +108 -0
  77. data/lib/legate/auth/error.rb +94 -0
  78. data/lib/legate/auth/exchanged_credential.rb +180 -0
  79. data/lib/legate/auth/excon_middleware.rb +285 -0
  80. data/lib/legate/auth/http_client_utils.rb +364 -0
  81. data/lib/legate/auth/manager.rb +531 -0
  82. data/lib/legate/auth/manager_store.rb +394 -0
  83. data/lib/legate/auth/middleware_factory.rb +290 -0
  84. data/lib/legate/auth/runner.rb +279 -0
  85. data/lib/legate/auth/scheme.rb +125 -0
  86. data/lib/legate/auth/schemes/api_key.rb +212 -0
  87. data/lib/legate/auth/schemes/google_service_account.rb +108 -0
  88. data/lib/legate/auth/schemes/http_bearer.rb +98 -0
  89. data/lib/legate/auth/schemes/oauth2.rb +396 -0
  90. data/lib/legate/auth/schemes/openid_connect.rb +346 -0
  91. data/lib/legate/auth/schemes/service_account.rb +388 -0
  92. data/lib/legate/auth/schemes.rb +40 -0
  93. data/lib/legate/auth/token_manager.rb +362 -0
  94. data/lib/legate/auth/token_store.rb +86 -0
  95. data/lib/legate/auth/tool_context_extension.rb +97 -0
  96. data/lib/legate/auth/tool_integration.rb +188 -0
  97. data/lib/legate/auth/url_guard.rb +81 -0
  98. data/lib/legate/auth.rb +453 -0
  99. data/lib/legate/callbacks/callback_context.rb +71 -0
  100. data/lib/legate/cli/agent_commands.rb +950 -0
  101. data/lib/legate/cli/auth_commands.rb +520 -0
  102. data/lib/legate/cli/base_command.rb +24 -0
  103. data/lib/legate/cli/deployment_commands.rb +934 -0
  104. data/lib/legate/cli/output_helper.rb +108 -0
  105. data/lib/legate/cli/session_commands.rb +138 -0
  106. data/lib/legate/cli/skaffold_commands.rb +223 -0
  107. data/lib/legate/cli/tool_commands.rb +261 -0
  108. data/lib/legate/cli/web_commands.rb +182 -0
  109. data/lib/legate/cli.rb +40 -0
  110. data/lib/legate/configuration/webhooks.rb +113 -0
  111. data/lib/legate/configuration.rb +39 -0
  112. data/lib/legate/definition_store.rb +23 -0
  113. data/lib/legate/errors.rb +118 -0
  114. data/lib/legate/event.rb +161 -0
  115. data/lib/legate/gemini_ai_beta_patch.rb +39 -0
  116. data/lib/legate/generators/agent_generator.rb +412 -0
  117. data/lib/legate/generators/code_validator.rb +48 -0
  118. data/lib/legate/generators/legate/install_generator.rb +35 -0
  119. data/lib/legate/generators/legate/templates/create_legate_tables.rb.tt +36 -0
  120. data/lib/legate/generators/legate/templates/initializer.rb +18 -0
  121. data/lib/legate/generators/runtime_tool_loader.rb +76 -0
  122. data/lib/legate/generators/tool_generator.rb +408 -0
  123. data/lib/legate/generators.rb +11 -0
  124. data/lib/legate/global_definition_registry.rb +506 -0
  125. data/lib/legate/global_tool_manager.rb +135 -0
  126. data/lib/legate/llm/adapter.rb +69 -0
  127. data/lib/legate/llm/gemini.rb +172 -0
  128. data/lib/legate/llm/ollama.rb +80 -0
  129. data/lib/legate/llm.rb +34 -0
  130. data/lib/legate/mcp/client.rb +320 -0
  131. data/lib/legate/mcp/connection/sse.rb +292 -0
  132. data/lib/legate/mcp/connection/stdio.rb +273 -0
  133. data/lib/legate/mcp/connection_manager.rb +103 -0
  134. data/lib/legate/mcp/server/legate_agent_adapter.rb +170 -0
  135. data/lib/legate/mcp/server/legate_direct_agent_adapter.rb +140 -0
  136. data/lib/legate/mcp/server/legate_tool_adapter.rb +119 -0
  137. data/lib/legate/mcp/tool_wrapper.rb +138 -0
  138. data/lib/legate/mcp/util/schema_converter.rb +134 -0
  139. data/lib/legate/mcp.rb +23 -0
  140. data/lib/legate/plan_executor.rb +375 -0
  141. data/lib/legate/planner.rb +839 -0
  142. data/lib/legate/rails/railtie.rb +43 -0
  143. data/lib/legate/rails.rb +9 -0
  144. data/lib/legate/redaction.rb +32 -0
  145. data/lib/legate/session.rb +299 -0
  146. data/lib/legate/session_service/active_record.rb +300 -0
  147. data/lib/legate/session_service/base.rb +68 -0
  148. data/lib/legate/session_service/event_broadcast.rb +74 -0
  149. data/lib/legate/session_service/in_memory.rb +188 -0
  150. data/lib/legate/tool/metadata_dsl.rb +122 -0
  151. data/lib/legate/tool.rb +276 -0
  152. data/lib/legate/tool_code_generator.rb +103 -0
  153. data/lib/legate/tool_context.rb +350 -0
  154. data/lib/legate/tool_loader.rb +39 -0
  155. data/lib/legate/tool_registry.rb +73 -0
  156. data/lib/legate/tool_result.rb +61 -0
  157. data/lib/legate/tools/agent_tool.rb +187 -0
  158. data/lib/legate/tools/base/http_client.rb +319 -0
  159. data/lib/legate/tools/base/safe_url.rb +56 -0
  160. data/lib/legate/tools/base_async_job_tool.rb +91 -0
  161. data/lib/legate/tools/calculator.rb +89 -0
  162. data/lib/legate/tools/cat_facts.rb +81 -0
  163. data/lib/legate/tools/check_job_status_tool.rb +48 -0
  164. data/lib/legate/tools/current_time_tool.rb +64 -0
  165. data/lib/legate/tools/echo.rb +43 -0
  166. data/lib/legate/tools/http_request_tool.rb +105 -0
  167. data/lib/legate/tools/random_number_tool.rb +64 -0
  168. data/lib/legate/tools/read_webpage_tool.rb +92 -0
  169. data/lib/legate/tools/sleepy_tool.rb +74 -0
  170. data/lib/legate/tools/webhook_tool.rb +146 -0
  171. data/lib/legate/version.rb +5 -0
  172. data/lib/legate/web/app.rb +984 -0
  173. data/lib/legate/web/public/css/main.css +4980 -0
  174. data/lib/legate/web/public/images/favicon-256.png +0 -0
  175. data/lib/legate/web/public/images/favicon-32.png +0 -0
  176. data/lib/legate/web/public/images/legate-logo-dark.png +0 -0
  177. data/lib/legate/web/public/images/legate-logo-light.png +0 -0
  178. data/lib/legate/web/public/js/legate.js +616 -0
  179. data/lib/legate/web/public/styles/main.scss +4402 -0
  180. data/lib/legate/web/routes/agent_authentication_routes.rb +530 -0
  181. data/lib/legate/web/routes/agent_definition_routes.rb +803 -0
  182. data/lib/legate/web/routes/agent_generator_routes.rb +80 -0
  183. data/lib/legate/web/routes/agent_interaction_routes.rb +734 -0
  184. data/lib/legate/web/routes/agent_runtime_routes.rb +323 -0
  185. data/lib/legate/web/routes/api_routes.rb +56 -0
  186. data/lib/legate/web/routes/authentication_routes.rb +1541 -0
  187. data/lib/legate/web/routes/core_routes.rb +111 -0
  188. data/lib/legate/web/routes/documentation_routes.rb +220 -0
  189. data/lib/legate/web/routes/tool_generator_routes.rb +81 -0
  190. data/lib/legate/web/routes/tools_ui_routes.rb +207 -0
  191. data/lib/legate/web/sass_compiler.rb +73 -0
  192. data/lib/legate/web/views/_active_session_info.slim +25 -0
  193. data/lib/legate/web/views/_activity_list.slim +55 -0
  194. data/lib/legate/web/views/_agent_card.slim +56 -0
  195. data/lib/legate/web/views/_agent_generator_modal.slim +382 -0
  196. data/lib/legate/web/views/_agent_status_controls.slim +71 -0
  197. data/lib/legate/web/views/_agent_tool_table.slim +74 -0
  198. data/lib/legate/web/views/_chat_message.slim +95 -0
  199. data/lib/legate/web/views/_display_agent_configuration.slim +26 -0
  200. data/lib/legate/web/views/_display_agent_description.slim +11 -0
  201. data/lib/legate/web/views/_display_agent_fallback.slim +15 -0
  202. data/lib/legate/web/views/_display_agent_hierarchy.slim +93 -0
  203. data/lib/legate/web/views/_display_agent_instruction.slim +17 -0
  204. data/lib/legate/web/views/_display_agent_mcp.slim +13 -0
  205. data/lib/legate/web/views/_display_agent_model.slim +17 -0
  206. data/lib/legate/web/views/_display_agent_name.slim +42 -0
  207. data/lib/legate/web/views/_display_agent_output_key.slim +26 -0
  208. data/lib/legate/web/views/_display_agent_type.slim +65 -0
  209. data/lib/legate/web/views/_edit_agent_configuration.slim +74 -0
  210. data/lib/legate/web/views/_edit_agent_description.slim +16 -0
  211. data/lib/legate/web/views/_edit_agent_fallback.slim +25 -0
  212. data/lib/legate/web/views/_edit_agent_hierarchy.slim +98 -0
  213. data/lib/legate/web/views/_edit_agent_instruction.slim +49 -0
  214. data/lib/legate/web/views/_edit_agent_mcp.slim +33 -0
  215. data/lib/legate/web/views/_edit_agent_model.slim +23 -0
  216. data/lib/legate/web/views/_edit_agent_output_key.slim +36 -0
  217. data/lib/legate/web/views/_edit_agent_tools.slim +40 -0
  218. data/lib/legate/web/views/_edit_agent_type.slim +67 -0
  219. data/lib/legate/web/views/_session_error.slim +4 -0
  220. data/lib/legate/web/views/_skeleton.slim +69 -0
  221. data/lib/legate/web/views/_tool_card.slim +9 -0
  222. data/lib/legate/web/views/_tool_generator_modal.slim +311 -0
  223. data/lib/legate/web/views/agent.slim +436 -0
  224. data/lib/legate/web/views/agent_auth.slim +562 -0
  225. data/lib/legate/web/views/agents.slim +369 -0
  226. data/lib/legate/web/views/auth.slim +112 -0
  227. data/lib/legate/web/views/auth_credential_detail.slim +327 -0
  228. data/lib/legate/web/views/auth_credentials.slim +261 -0
  229. data/lib/legate/web/views/auth_debug.slim +94 -0
  230. data/lib/legate/web/views/auth_mapping_detail.slim +151 -0
  231. data/lib/legate/web/views/auth_mapping_new.slim +123 -0
  232. data/lib/legate/web/views/auth_mappings.slim +120 -0
  233. data/lib/legate/web/views/auth_scheme_detail.slim +274 -0
  234. data/lib/legate/web/views/auth_schemes.slim +259 -0
  235. data/lib/legate/web/views/auth_test.slim +418 -0
  236. data/lib/legate/web/views/chat.slim +192 -0
  237. data/lib/legate/web/views/docs_index.slim +105 -0
  238. data/lib/legate/web/views/docs_show.slim +105 -0
  239. data/lib/legate/web/views/error_404.slim +5 -0
  240. data/lib/legate/web/views/index.slim +148 -0
  241. data/lib/legate/web/views/layout.slim +144 -0
  242. data/lib/legate/web/views/tool_detail.slim +87 -0
  243. data/lib/legate/web/views/tools.slim +50 -0
  244. data/lib/legate/web/webhook_listener.rb +367 -0
  245. data/lib/legate/web.rb +9 -0
  246. data/lib/legate.rb +220 -0
  247. data/public/docs/advanced/callbacks.md +828 -0
  248. data/public/docs/advanced/mcp_schema_conversion.md +59 -0
  249. data/public/docs/authentication/api_reference/config.md +210 -0
  250. data/public/docs/authentication/api_reference/credential.md +246 -0
  251. data/public/docs/authentication/api_reference/encryption.md +218 -0
  252. data/public/docs/authentication/api_reference/exchanged_credential.md +271 -0
  253. data/public/docs/authentication/api_reference/excon_middleware.md +175 -0
  254. data/public/docs/authentication/api_reference/index.md +30 -0
  255. data/public/docs/authentication/api_reference/scheme.md +250 -0
  256. data/public/docs/authentication/api_reference/schemes/api_key.md +175 -0
  257. data/public/docs/authentication/api_reference/schemes/google_service_account.md +221 -0
  258. data/public/docs/authentication/api_reference/schemes/http_bearer.md +169 -0
  259. data/public/docs/authentication/api_reference/schemes/oauth2.md +343 -0
  260. data/public/docs/authentication/api_reference/schemes/oidc.md +73 -0
  261. data/public/docs/authentication/api_reference/schemes/openid_connect.md +311 -0
  262. data/public/docs/authentication/api_reference/schemes/service_account.md +287 -0
  263. data/public/docs/authentication/api_reference/token_manager.md +221 -0
  264. data/public/docs/authentication/api_reference/token_store.md +146 -0
  265. data/public/docs/authentication/api_reference/tool_context_extension.md +166 -0
  266. data/public/docs/authentication/guides/api_key.md +190 -0
  267. data/public/docs/authentication/guides/bearer.md +172 -0
  268. data/public/docs/authentication/guides/configuration.md +255 -0
  269. data/public/docs/authentication/guides/custom_flow.md +523 -0
  270. data/public/docs/authentication/guides/index.md +24 -0
  271. data/public/docs/authentication/guides/migration.md +435 -0
  272. data/public/docs/authentication/guides/oauth2.md +252 -0
  273. data/public/docs/authentication/guides/oidc.md +241 -0
  274. data/public/docs/authentication/guides/overview.md +155 -0
  275. data/public/docs/authentication/guides/secure_storage.md +301 -0
  276. data/public/docs/authentication/guides/service_account.md +228 -0
  277. data/public/docs/authentication/guides/token_lifecycle.md +295 -0
  278. data/public/docs/authentication/guides/web_ui_integration.md +504 -0
  279. data/public/docs/authentication/index.md +58 -0
  280. data/public/docs/authentication/troubleshooting/credential_storage.md +550 -0
  281. data/public/docs/authentication/troubleshooting/environment_variables.md +540 -0
  282. data/public/docs/authentication/troubleshooting/index.md +11 -0
  283. data/public/docs/authentication/troubleshooting/oauth2_issues.md +220 -0
  284. data/public/docs/authentication/troubleshooting/oidc_issues.md +412 -0
  285. data/public/docs/authentication/troubleshooting/token_refresh.md +338 -0
  286. data/public/docs/cli/legate_cli_usage.md +363 -0
  287. data/public/docs/core_concepts/legate_agent_lifecycle.md +124 -0
  288. data/public/docs/core_concepts/legate_architecture_overview.md +110 -0
  289. data/public/docs/core_concepts/legate_configuration.md +116 -0
  290. data/public/docs/core_concepts/legate_definition_store.md +102 -0
  291. data/public/docs/core_concepts/legate_planner.md +94 -0
  292. data/public/docs/core_concepts/legate_session_service.md +104 -0
  293. data/public/docs/error_handling/legate_error_handling.md +122 -0
  294. data/public/docs/examples.md +199 -0
  295. data/public/docs/getting_started.md +111 -0
  296. data/public/docs/guides/agentic_agents.md +137 -0
  297. data/public/docs/guides/ai_code_generators.md +437 -0
  298. data/public/docs/guides/auto_loading.md +326 -0
  299. data/public/docs/guides/configuring_agent_webhooks.md +219 -0
  300. data/public/docs/guides/http_client_usage.md +264 -0
  301. data/public/docs/guides/llm_providers.md +137 -0
  302. data/public/docs/guides/mcp_client_integration.md +232 -0
  303. data/public/docs/guides/mcp_server_exposure.md +206 -0
  304. data/public/docs/guides/rails_integration.md +128 -0
  305. data/public/docs/guides/sending_outbound_webhooks.md +227 -0
  306. data/public/docs/guides/streaming.md +112 -0
  307. data/public/docs/guides/webhooks.md +288 -0
  308. data/public/docs/introduction.md +51 -0
  309. data/public/docs/multi_agent_systems/advanced_features.md +57 -0
  310. data/public/docs/multi_agent_systems/agent_delegation.md +190 -0
  311. data/public/docs/multi_agent_systems/agent_hierarchy.md +49 -0
  312. data/public/docs/multi_agent_systems/state_management.md +47 -0
  313. data/public/docs/multi_agent_systems/workflow_agents.md +72 -0
  314. data/public/docs/tools/legate_built_in_tools.md +332 -0
  315. data/public/docs/tools/legate_tools_and_registry.md +263 -0
  316. data/public/docs/web_ui/legate_web_ui.md +137 -0
  317. metadata +823 -0
@@ -0,0 +1,520 @@
1
+ # File: lib/legate/cli/auth_commands.rb
2
+ # frozen_string_literal: true
3
+
4
+ require 'thor'
5
+ require_relative 'base_command'
6
+ require 'cli/ui'
7
+ require_relative '../../legate'
8
+
9
+ module Legate
10
+ module CLI
11
+ # Helper module for auth command formatting
12
+ module AuthCommandHelpers
13
+ # Mask sensitive values for display
14
+ def mask_sensitive_value(value)
15
+ return '(not set)' if value.nil? || value.empty?
16
+ return value if value.start_with?('ENV:') # Show env var references as-is
17
+
18
+ if value.length <= 8
19
+ '********'
20
+ else
21
+ "#{value[0, 4]}********#{value[-4..]}"
22
+ end
23
+ end
24
+
25
+ # Get scheme type description
26
+ def scheme_type_description(scheme_type)
27
+ case scheme_type.to_sym
28
+ when :api_key then 'API Key authentication'
29
+ when :http_bearer then 'HTTP Bearer token'
30
+ when :oauth2 then 'OAuth 2.0 flow'
31
+ when :oidc, :openid_connect then 'OpenID Connect'
32
+ when :service_account then 'Service Account'
33
+ when :google_service_account then 'Google Service Account'
34
+ else scheme_type.to_s
35
+ end
36
+ end
37
+
38
+ # Get credential type description
39
+ def credential_type_description(auth_type)
40
+ case auth_type.to_sym
41
+ when :api_key then 'API Key'
42
+ when :http_bearer then 'Bearer Token'
43
+ when :oauth2 then 'OAuth2 Client'
44
+ when :oidc then 'OIDC Client'
45
+ when :service_account then 'Service Account'
46
+ when :google_service_account then 'Google Service Account'
47
+ when :basic then 'Basic Auth'
48
+ else auth_type.to_s
49
+ end
50
+ end
51
+
52
+ # Sensitive field names
53
+ def sensitive_field?(name)
54
+ %i[api_key client_secret bearer_token password private_key service_account_key token].include?(name.to_sym)
55
+ end
56
+
57
+ # Print styled header
58
+ def print_header(text)
59
+ puts ::CLI::UI.fmt("{{bold:#{text}}}")
60
+ end
61
+
62
+ # Print styled table row
63
+ def print_row(label, value, indent: 2)
64
+ padding = ' ' * indent
65
+ puts "#{padding}#{::CLI::UI.fmt("{{cyan:#{label}:}}")} #{value}"
66
+ end
67
+
68
+ # Ensure auth manager is ready
69
+ def auth_manager
70
+ @auth_manager ||= begin
71
+ manager = Legate::Auth::Manager.instance
72
+ manager.load_from_store
73
+ manager
74
+ end
75
+ end
76
+ end
77
+
78
+ # Scheme management subcommands
79
+ class AuthSchemeCommands < BaseCommand
80
+ include AuthCommandHelpers
81
+
82
+ namespace 'auth:schemes'
83
+
84
+ desc 'list', 'List all registered authentication schemes'
85
+ def list
86
+ schemes = auth_manager.instance_variable_get(:@schemes) || {}
87
+
88
+ if schemes.empty?
89
+ puts ::CLI::UI.fmt('{{yellow:No authentication schemes registered.}}')
90
+ return
91
+ end
92
+
93
+ print_header("Authentication Schemes (#{schemes.size})")
94
+ puts
95
+
96
+ schemes.each do |name, scheme|
97
+ puts ::CLI::UI.fmt(" {{bold:#{name}}} {{gray:(#{scheme.scheme_type})}}")
98
+ puts " #{scheme_type_description(scheme.scheme_type)}"
99
+ puts
100
+ end
101
+ end
102
+
103
+ desc 'show NAME', 'Show details for a specific scheme'
104
+ def show(name)
105
+ scheme = auth_manager.get_scheme(name.to_sym)
106
+
107
+ unless scheme
108
+ puts ::CLI::UI.fmt("{{red:Scheme not found:}} #{name}")
109
+ exit 1
110
+ end
111
+
112
+ print_header("Scheme: #{name}")
113
+ print_row('Type', scheme.scheme_type)
114
+ print_row('Class', scheme.class.name.split('::').last)
115
+ print_row('Description', scheme_type_description(scheme.scheme_type))
116
+
117
+ # Show scheme-specific config
118
+ config = scheme.to_h
119
+ config.each do |key, value|
120
+ next if key == :type
121
+
122
+ print_row(key.to_s.capitalize, value) if value
123
+ end
124
+ end
125
+
126
+ desc 'create NAME', 'Create a new authentication scheme'
127
+ method_option :type, type: :string, required: true,
128
+ desc: 'Scheme type (api_key, http_bearer, oauth2, oidc, service_account, google_service_account)'
129
+ method_option :authorization_url, type: :string, desc: 'OAuth2/OIDC authorization URL'
130
+ method_option :token_url, type: :string, desc: 'OAuth2/OIDC/Service Account token URL'
131
+ method_option :userinfo_url, type: :string, desc: 'OIDC userinfo URL'
132
+ method_option :scopes, type: :string, desc: 'Space-separated scopes'
133
+ method_option :use_pkce, type: :boolean, default: false, desc: 'Use PKCE for OAuth2/OIDC'
134
+ method_option :revocation_url, type: :string, desc: 'OAuth2 revocation URL'
135
+ def create(name)
136
+ scheme_name = name.to_sym
137
+ scheme_type = options[:type].to_sym
138
+
139
+ if auth_manager.get_scheme(scheme_name)
140
+ puts ::CLI::UI.fmt("{{red:Scheme already exists:}} #{name}")
141
+ exit 1
142
+ end
143
+
144
+ scheme = case scheme_type
145
+ when :api_key
146
+ Legate::Auth::Schemes::ApiKey.new
147
+ when :http_bearer
148
+ Legate::Auth::Schemes::HTTPBearer.new
149
+ when :oauth2
150
+ Legate::Auth::Schemes::OAuth2.new(
151
+ authorization_url: options[:authorization_url],
152
+ token_url: options[:token_url],
153
+ scopes: options[:scopes]&.split(/\s+/),
154
+ use_pkce: options[:use_pkce],
155
+ revocation_url: options[:revocation_url]
156
+ )
157
+ when :oidc, :openid_connect
158
+ Legate::Auth::Schemes::OpenIDConnect.new(
159
+ authorization_url: options[:authorization_url],
160
+ token_url: options[:token_url],
161
+ userinfo_url: options[:userinfo_url],
162
+ scopes: options[:scopes]&.split(/\s+/),
163
+ use_pkce: options[:use_pkce]
164
+ )
165
+ when :service_account
166
+ Legate::Auth::Schemes::ServiceAccount.new(
167
+ token_url: options[:token_url],
168
+ scopes: options[:scopes]&.split(/\s+/)
169
+ )
170
+ when :google_service_account
171
+ Legate::Auth::Schemes::GoogleServiceAccount.new(
172
+ scopes: options[:scopes]&.split(/\s+/)
173
+ )
174
+ else
175
+ puts ::CLI::UI.fmt("{{red:Unsupported scheme type:}} #{scheme_type}")
176
+ exit 1
177
+ end
178
+
179
+ auth_manager.register_scheme(scheme, scheme_name)
180
+ puts ::CLI::UI.fmt("{{green:✓}} Scheme '#{name}' created successfully")
181
+ end
182
+
183
+ desc 'delete NAME', 'Delete an authentication scheme'
184
+ method_option :force, type: :boolean, default: false, desc: 'Force delete even if in use'
185
+ def delete(name)
186
+ scheme_name = name.to_sym
187
+
188
+ unless auth_manager.get_scheme(scheme_name)
189
+ puts ::CLI::UI.fmt("{{red:Scheme not found:}} #{name}")
190
+ exit 1
191
+ end
192
+
193
+ # Check for dependent mappings
194
+ url_mappings = auth_manager.instance_variable_get(:@url_mappings) || []
195
+ dependent = url_mappings.select { |m| m[:scheme_name] == scheme_name }
196
+
197
+ if dependent.any? && !options[:force]
198
+ puts ::CLI::UI.fmt("{{red:Cannot delete}} - scheme is used by #{dependent.size} URL mapping(s)")
199
+ puts 'Use --force to delete anyway'
200
+ exit 1
201
+ end
202
+
203
+ auth_manager.unregister_scheme(scheme_name)
204
+ puts ::CLI::UI.fmt("{{green:✓}} Scheme '#{name}' deleted")
205
+ end
206
+ end
207
+
208
+ # Credential management subcommands
209
+ class AuthCredentialCommands < BaseCommand
210
+ include AuthCommandHelpers
211
+
212
+ namespace 'auth:credentials'
213
+
214
+ desc 'list', 'List all registered credentials'
215
+ def list
216
+ credentials = auth_manager.instance_variable_get(:@credentials) || {}
217
+
218
+ if credentials.empty?
219
+ puts ::CLI::UI.fmt('{{yellow:No credentials registered.}}')
220
+ return
221
+ end
222
+
223
+ print_header("Credentials (#{credentials.size})")
224
+ puts
225
+
226
+ credentials.each do |name, cred|
227
+ puts ::CLI::UI.fmt(" {{bold:#{name}}} {{gray:(#{cred.auth_type})}}")
228
+
229
+ # Show masked key info
230
+ case cred.auth_type
231
+ when :api_key
232
+ puts " API Key: #{mask_sensitive_value(cred[:api_key, resolve_env: false])}"
233
+ when :http_bearer
234
+ puts " Token: #{mask_sensitive_value(cred[:bearer_token, resolve_env: false])}"
235
+ when :oauth2, :oidc
236
+ puts " Client ID: #{cred[:client_id, resolve_env: false]}"
237
+ when :service_account, :google_service_account
238
+ puts ' Service Account Key: ********'
239
+ end
240
+ puts
241
+ end
242
+ end
243
+
244
+ desc 'show NAME', 'Show details for a specific credential'
245
+ def show(name)
246
+ credential = auth_manager.get_credential(name.to_sym)
247
+
248
+ unless credential
249
+ puts ::CLI::UI.fmt("{{red:Credential not found:}} #{name}")
250
+ exit 1
251
+ end
252
+
253
+ print_header("Credential: #{name}")
254
+ print_row('Type', credential_type_description(credential.auth_type))
255
+
256
+ credential.to_h(resolve_env: false).each do |key, value|
257
+ next if key == :auth_type
258
+
259
+ display_value = sensitive_field?(key) ? mask_sensitive_value(value.to_s) : value
260
+ print_row(key.to_s, display_value)
261
+ end
262
+ end
263
+
264
+ desc 'create NAME', 'Create a new credential'
265
+ method_option :type, type: :string, required: true,
266
+ desc: 'Credential type (api_key, http_bearer, oauth2, oidc, service_account, google_service_account, basic)'
267
+ method_option :api_key, type: :string, desc: 'API key value (or ENV:VAR_NAME)'
268
+ method_option :bearer_token, type: :string, desc: 'Bearer token (or ENV:VAR_NAME)'
269
+ method_option :client_id, type: :string, desc: 'OAuth2/OIDC client ID'
270
+ method_option :client_secret, type: :string, desc: 'OAuth2/OIDC client secret (or ENV:VAR_NAME)'
271
+ method_option :redirect_uri, type: :string, desc: 'OAuth2/OIDC redirect URI'
272
+ method_option :username, type: :string, desc: 'Basic auth username'
273
+ method_option :password, type: :string, desc: 'Basic auth password (or ENV:VAR_NAME)'
274
+ method_option :service_account_key, type: :string, desc: 'Service account JSON key (or ENV:VAR_NAME)'
275
+ method_option :service_account_key_file, type: :string, desc: 'Path to service account key file'
276
+ def create(name)
277
+ cred_name = name.to_sym
278
+ auth_type = options[:type].to_sym
279
+
280
+ if auth_manager.get_credential(cred_name)
281
+ puts ::CLI::UI.fmt("{{red:Credential already exists:}} #{name}")
282
+ exit 1
283
+ end
284
+
285
+ attrs = { auth_type: auth_type }
286
+
287
+ case auth_type
288
+ when :api_key
289
+ attrs[:api_key] = options[:api_key] || prompt_for('API Key')
290
+ when :http_bearer
291
+ attrs[:bearer_token] = options[:bearer_token] || prompt_for('Bearer Token')
292
+ when :oauth2, :oidc
293
+ attrs[:client_id] = options[:client_id] || prompt_for('Client ID')
294
+ attrs[:client_secret] = options[:client_secret]
295
+ attrs[:redirect_uri] = options[:redirect_uri] if options[:redirect_uri]
296
+ when :service_account, :google_service_account
297
+ attrs[:service_account_key] = if options[:service_account_key_file]
298
+ File.read(options[:service_account_key_file])
299
+ else
300
+ options[:service_account_key] || prompt_for('Service Account Key JSON')
301
+ end
302
+ when :basic
303
+ attrs[:username] = options[:username] || prompt_for('Username')
304
+ attrs[:password] = options[:password] || prompt_for('Password')
305
+ else
306
+ puts ::CLI::UI.fmt("{{red:Unsupported credential type:}} #{auth_type}")
307
+ exit 1
308
+ end
309
+
310
+ credential = Legate::Auth::Credential.new(**attrs)
311
+ auth_manager.register_credential(credential, cred_name)
312
+ puts ::CLI::UI.fmt("{{green:✓}} Credential '#{name}' created successfully")
313
+ rescue Legate::Auth::CredentialError => e
314
+ puts ::CLI::UI.fmt("{{red:Invalid credential:}} #{e.message}")
315
+ exit 1
316
+ end
317
+
318
+ desc 'delete NAME', 'Delete a credential'
319
+ method_option :force, type: :boolean, default: false, desc: 'Force delete even if in use'
320
+ def delete(name)
321
+ cred_name = name.to_sym
322
+
323
+ unless auth_manager.get_credential(cred_name)
324
+ puts ::CLI::UI.fmt("{{red:Credential not found:}} #{name}")
325
+ exit 1
326
+ end
327
+
328
+ # Check for dependent mappings
329
+ url_mappings = auth_manager.instance_variable_get(:@url_mappings) || []
330
+ dependent = url_mappings.select { |m| m[:credential_name] == cred_name }
331
+
332
+ if dependent.any? && !options[:force]
333
+ puts ::CLI::UI.fmt("{{red:Cannot delete}} - credential is used by #{dependent.size} URL mapping(s)")
334
+ puts 'Use --force to delete anyway'
335
+ exit 1
336
+ end
337
+
338
+ auth_manager.unregister_credential(cred_name)
339
+ puts ::CLI::UI.fmt("{{green:✓}} Credential '#{name}' deleted")
340
+ end
341
+
342
+ desc 'test NAME', 'Test a credential'
343
+ method_option :url, type: :string, desc: 'URL to test the credential against'
344
+ def test(name)
345
+ credential = auth_manager.get_credential(name.to_sym)
346
+
347
+ unless credential
348
+ puts ::CLI::UI.fmt("{{red:Credential not found:}} #{name}")
349
+ exit 1
350
+ end
351
+
352
+ print_header("Testing credential: #{name}")
353
+ puts
354
+
355
+ # Basic validation
356
+ begin
357
+ credential.to_h(resolve_env: true)
358
+ puts ::CLI::UI.fmt(' {{green:✓}} Basic validation passed')
359
+ rescue Legate::Auth::EnvironmentVariableNotFoundError => e
360
+ puts ::CLI::UI.fmt(" {{red:✗}} Environment variable not found: #{e.message}")
361
+ exit 1
362
+ end
363
+
364
+ # Type-specific validation
365
+ case credential.auth_type
366
+ when :api_key
367
+ api_key = credential[:api_key]
368
+ if api_key && !api_key.empty?
369
+ puts ::CLI::UI.fmt(' {{green:✓}} API key is present')
370
+ else
371
+ puts ::CLI::UI.fmt(' {{red:✗}} API key is empty')
372
+ end
373
+ when :oauth2, :oidc
374
+ client_id = credential[:client_id]
375
+ client_secret = credential[:client_secret]
376
+ puts client_id ? ::CLI::UI.fmt(' {{green:✓}} Client ID is present') : ::CLI::UI.fmt(' {{red:✗}} Client ID missing')
377
+ puts client_secret ? ::CLI::UI.fmt(' {{green:✓}} Client secret is present') : ::CLI::UI.fmt(' {{yellow:⚠}} Client secret not set')
378
+ when :google_service_account
379
+ begin
380
+ key_data = credential[:service_account_key]
381
+ parsed = JSON.parse(key_data)
382
+ required = %w[type project_id private_key_id private_key client_email client_id]
383
+ missing = required.reject { |f| parsed.key?(f) }
384
+ if missing.empty?
385
+ puts ::CLI::UI.fmt(' {{green:✓}} Service account key is valid JSON with all required fields')
386
+ else
387
+ puts ::CLI::UI.fmt(" {{red:✗}} Missing fields: #{missing.join(', ')}")
388
+ end
389
+ rescue JSON::ParserError
390
+ puts ::CLI::UI.fmt(' {{red:✗}} Service account key is not valid JSON')
391
+ end
392
+ end
393
+
394
+ puts
395
+ puts ::CLI::UI.fmt('{{green:Test complete}}')
396
+ end
397
+
398
+ private
399
+
400
+ def prompt_for(field)
401
+ print "#{field}: "
402
+ $stdin.gets.chomp
403
+ end
404
+ end
405
+
406
+ # URL mapping management subcommands
407
+ class AuthMappingCommands < BaseCommand
408
+ include AuthCommandHelpers
409
+
410
+ namespace 'auth:mappings'
411
+
412
+ desc 'list', 'List all URL-to-auth mappings'
413
+ def list
414
+ mappings = auth_manager.instance_variable_get(:@url_mappings) || []
415
+
416
+ if mappings.empty?
417
+ puts ::CLI::UI.fmt('{{yellow:No URL mappings registered.}}')
418
+ return
419
+ end
420
+
421
+ print_header("URL Mappings (#{mappings.size})")
422
+ puts
423
+
424
+ mappings.each_with_index do |mapping, idx|
425
+ pattern = mapping[:pattern].is_a?(Regexp) ? mapping[:pattern].source : mapping[:pattern].to_s
426
+ pattern_type = mapping[:pattern].is_a?(Regexp) ? 'regex' : 'string'
427
+
428
+ puts ::CLI::UI.fmt(" {{bold:[#{idx}]}} #{pattern} {{gray:(#{pattern_type})}}")
429
+ puts " Scheme: #{mapping[:scheme_name]}, Credential: #{mapping[:credential_name]}"
430
+ puts
431
+ end
432
+ end
433
+
434
+ desc 'create', 'Create a new URL mapping'
435
+ method_option :pattern, type: :string, required: true, desc: 'URL pattern (string or regex)'
436
+ method_option :scheme, type: :string, required: true, desc: 'Scheme name to use'
437
+ method_option :credential, type: :string, required: true, desc: 'Credential name to use'
438
+ method_option :regex, type: :boolean, default: false, desc: 'Treat pattern as regex'
439
+ def create
440
+ scheme_name = options[:scheme].to_sym
441
+ cred_name = options[:credential].to_sym
442
+
443
+ unless auth_manager.get_scheme(scheme_name)
444
+ puts ::CLI::UI.fmt("{{red:Scheme not found:}} #{options[:scheme]}")
445
+ exit 1
446
+ end
447
+
448
+ unless auth_manager.get_credential(cred_name)
449
+ puts ::CLI::UI.fmt("{{red:Credential not found:}} #{options[:credential]}")
450
+ exit 1
451
+ end
452
+
453
+ pattern = if options[:regex]
454
+ begin
455
+ Regexp.new(options[:pattern])
456
+ rescue RegexpError => e
457
+ puts ::CLI::UI.fmt("{{red:Invalid regex:}} #{e.message}")
458
+ exit 1
459
+ end
460
+ else
461
+ options[:pattern]
462
+ end
463
+
464
+ auth_manager.register_url_mapping(pattern, scheme_name, cred_name)
465
+ puts ::CLI::UI.fmt('{{green:✓}} URL mapping created successfully')
466
+ end
467
+
468
+ desc 'delete INDEX', 'Delete a URL mapping by index'
469
+ def delete(index)
470
+ idx = index.to_i
471
+ mappings = auth_manager.instance_variable_get(:@url_mappings) || []
472
+
473
+ if idx < 0 || idx >= mappings.size
474
+ puts ::CLI::UI.fmt("{{red:Invalid index:}} #{index} (valid range: 0-#{mappings.size - 1})")
475
+ exit 1
476
+ end
477
+
478
+ auth_manager.remove_url_mapping(idx)
479
+ puts ::CLI::UI.fmt("{{green:✓}} URL mapping [#{index}] deleted")
480
+ end
481
+ end
482
+
483
+ # Main auth commands class that registers subcommands
484
+ class AuthCommands < BaseCommand
485
+ namespace :auth
486
+
487
+ desc 'schemes SUBCOMMAND', 'Manage authentication schemes'
488
+ subcommand 'schemes', AuthSchemeCommands
489
+
490
+ desc 'credentials SUBCOMMAND', 'Manage authentication credentials'
491
+ subcommand 'credentials', AuthCredentialCommands
492
+
493
+ desc 'mappings SUBCOMMAND', 'Manage URL-to-auth mappings'
494
+ subcommand 'mappings', AuthMappingCommands
495
+
496
+ desc 'status', 'Show authentication system status'
497
+ def status
498
+ manager = Legate::Auth::Manager.instance
499
+ manager.load_from_store
500
+
501
+ schemes = manager.instance_variable_get(:@schemes) || {}
502
+ credentials = manager.instance_variable_get(:@credentials) || {}
503
+ mappings = manager.instance_variable_get(:@url_mappings) || []
504
+
505
+ puts ::CLI::UI.fmt('{{bold:Authentication System Status}}')
506
+ puts
507
+ puts ::CLI::UI.fmt(" Schemes: {{cyan:#{schemes.size}}}")
508
+ puts ::CLI::UI.fmt(" Credentials: {{cyan:#{credentials.size}}}")
509
+ puts ::CLI::UI.fmt(" Mappings: {{cyan:#{mappings.size}}}")
510
+ puts
511
+
512
+ puts ::CLI::UI.fmt(' {{gray:Scheme types:}} ' + schemes.values.map(&:scheme_type).uniq.join(', ')) if schemes.any?
513
+
514
+ return unless credentials.any?
515
+
516
+ puts ::CLI::UI.fmt(' {{gray:Credential types:}} ' + credentials.values.map(&:auth_type).uniq.join(', '))
517
+ end
518
+ end
519
+ end
520
+ end
@@ -0,0 +1,24 @@
1
+ # File: lib/legate/cli/base_command.rb
2
+ # frozen_string_literal: true
3
+
4
+ require 'thor'
5
+
6
+ module Legate
7
+ module CLI
8
+ # Base class for every Legate CLI command group.
9
+ #
10
+ # Thor 1.5 ships a built-in `tree` command on the base Thor class. Left
11
+ # alone it leaks into help output namespaced by the implementation class
12
+ # (e.g. `legate tool_commands tree`), which looks like a bug. We hide it
13
+ # here once so all command groups inherit clean help output.
14
+ class BaseCommand < Thor
15
+ # The method body is intentionally a bare `super`: redefining `tree` here
16
+ # is what lets the preceding `hide: true` take effect, suppressing Thor's
17
+ # inherited (visible) command without changing its behavior.
18
+ desc 'tree', 'Print a tree of all available commands', hide: true
19
+ def tree # rubocop:disable Lint/UselessMethodDefinition
20
+ super
21
+ end
22
+ end
23
+ end
24
+ end