rubino-agent 0.3.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 (376) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +115 -0
  4. data/.rubocop_todo.yml +955 -0
  5. data/.ruby-version +1 -0
  6. data/AGENTS.md +97 -0
  7. data/CHANGELOG.md +344 -0
  8. data/CONTRIBUTING.md +69 -0
  9. data/LICENSE +21 -0
  10. data/README.md +200 -0
  11. data/Rakefile +8 -0
  12. data/docs/agents.md +190 -0
  13. data/docs/api/v1.md +414 -0
  14. data/docs/architecture.md +177 -0
  15. data/docs/commands.md +375 -0
  16. data/docs/configuration.md +590 -0
  17. data/docs/getting-started.md +143 -0
  18. data/docs/jobs.md +332 -0
  19. data/docs/mcp.md +128 -0
  20. data/docs/memory.md +98 -0
  21. data/docs/models-and-keys.md +173 -0
  22. data/docs/oauth-providers.md +145 -0
  23. data/docs/plugins.md +195 -0
  24. data/docs/security.md +145 -0
  25. data/docs/skills.md +322 -0
  26. data/docs/tools.md +395 -0
  27. data/docs/troubleshooting.md +73 -0
  28. data/exe/rubino +9 -0
  29. data/install.sh +275 -0
  30. data/lib/rubino/active_skill.rb +50 -0
  31. data/lib/rubino/agent/agent_registry.rb +120 -0
  32. data/lib/rubino/agent/backoff_policy.rb +116 -0
  33. data/lib/rubino/agent/definition.rb +128 -0
  34. data/lib/rubino/agent/degenerate_recovery.rb +271 -0
  35. data/lib/rubino/agent/fallback_chain.rb +194 -0
  36. data/lib/rubino/agent/iteration_budget.rb +50 -0
  37. data/lib/rubino/agent/loop.rb +617 -0
  38. data/lib/rubino/agent/model_call_runner.rb +383 -0
  39. data/lib/rubino/agent/prompts/build.txt +69 -0
  40. data/lib/rubino/agent/prompts/compaction.txt +20 -0
  41. data/lib/rubino/agent/prompts/explore.txt +19 -0
  42. data/lib/rubino/agent/prompts/general.txt +20 -0
  43. data/lib/rubino/agent/prompts/plan.txt +31 -0
  44. data/lib/rubino/agent/response_validator.rb +70 -0
  45. data/lib/rubino/agent/router.rb +65 -0
  46. data/lib/rubino/agent/runner.rb +195 -0
  47. data/lib/rubino/agent/tool_executor.rb +402 -0
  48. data/lib/rubino/agent/truncation_continuation.rb +137 -0
  49. data/lib/rubino/api/middleware/auth.rb +43 -0
  50. data/lib/rubino/api/middleware/error_handler.rb +65 -0
  51. data/lib/rubino/api/middleware/json_parser.rb +100 -0
  52. data/lib/rubino/api/middleware/observability.rb +59 -0
  53. data/lib/rubino/api/middleware/rate_limit.rb +136 -0
  54. data/lib/rubino/api/operations/approvals/decide_operation.rb +49 -0
  55. data/lib/rubino/api/operations/clarifications/decide_operation.rb +44 -0
  56. data/lib/rubino/api/operations/cron_jobs/create_operation.rb +46 -0
  57. data/lib/rubino/api/operations/cron_jobs/delete_operation.rb +36 -0
  58. data/lib/rubino/api/operations/cron_jobs/list_operation.rb +55 -0
  59. data/lib/rubino/api/operations/cron_jobs/pause_operation.rb +34 -0
  60. data/lib/rubino/api/operations/cron_jobs/resume_operation.rb +34 -0
  61. data/lib/rubino/api/operations/cron_jobs/schedule_validation.rb +30 -0
  62. data/lib/rubino/api/operations/cron_jobs/show_operation.rb +32 -0
  63. data/lib/rubino/api/operations/cron_jobs/trigger_operation.rb +38 -0
  64. data/lib/rubino/api/operations/cron_jobs/update_operation.rb +42 -0
  65. data/lib/rubino/api/operations/files/read_operation.rb +40 -0
  66. data/lib/rubino/api/operations/files/upload_operation.rb +175 -0
  67. data/lib/rubino/api/operations/health_operation.rb +46 -0
  68. data/lib/rubino/api/operations/memory/delete_operation.rb +32 -0
  69. data/lib/rubino/api/operations/memory/index_operation.rb +80 -0
  70. data/lib/rubino/api/operations/memory/stats_operation.rb +28 -0
  71. data/lib/rubino/api/operations/metrics_operation.rb +18 -0
  72. data/lib/rubino/api/operations/mode/show_operation.rb +29 -0
  73. data/lib/rubino/api/operations/mode/update_operation.rb +42 -0
  74. data/lib/rubino/api/operations/models/list_operation.rb +45 -0
  75. data/lib/rubino/api/operations/oauth/connections/disconnect_operation.rb +77 -0
  76. data/lib/rubino/api/operations/oauth/connections/list_operation.rb +36 -0
  77. data/lib/rubino/api/operations/oauth/providers/callback_operation.rb +82 -0
  78. data/lib/rubino/api/operations/oauth/providers/connect_operation.rb +44 -0
  79. data/lib/rubino/api/operations/oauth/providers/list_operation.rb +35 -0
  80. data/lib/rubino/api/operations/oauth/serializer.rb +21 -0
  81. data/lib/rubino/api/operations/runs/create_operation.rb +77 -0
  82. data/lib/rubino/api/operations/runs/events_operation.rb +195 -0
  83. data/lib/rubino/api/operations/runs/stop_operation.rb +34 -0
  84. data/lib/rubino/api/operations/sessions/create_operation.rb +46 -0
  85. data/lib/rubino/api/operations/sessions/delete_operation.rb +33 -0
  86. data/lib/rubino/api/operations/sessions/index_operation.rb +82 -0
  87. data/lib/rubino/api/operations/sessions/retry_operation.rb +45 -0
  88. data/lib/rubino/api/operations/sessions/show_operation.rb +59 -0
  89. data/lib/rubino/api/operations/sessions/undo_operation.rb +38 -0
  90. data/lib/rubino/api/operations/skills/list_operation.rb +34 -0
  91. data/lib/rubino/api/operations/skills/toggle_operation.rb +40 -0
  92. data/lib/rubino/api/operations/tasks/index_operation.rb +30 -0
  93. data/lib/rubino/api/operations/tasks/serializer.rb +60 -0
  94. data/lib/rubino/api/operations/tasks/show_operation.rb +33 -0
  95. data/lib/rubino/api/operations/tasks/stop_operation.rb +47 -0
  96. data/lib/rubino/api/request.rb +54 -0
  97. data/lib/rubino/api/responses.rb +64 -0
  98. data/lib/rubino/api/router.rb +72 -0
  99. data/lib/rubino/api/schemas.rb +103 -0
  100. data/lib/rubino/api/server.rb +102 -0
  101. data/lib/rubino/api/tls.rb +108 -0
  102. data/lib/rubino/attachments/classification.rb +16 -0
  103. data/lib/rubino/attachments/classify.rb +171 -0
  104. data/lib/rubino/attachments/defang.rb +47 -0
  105. data/lib/rubino/attachments/policy.rb +36 -0
  106. data/lib/rubino/attachments/preamble.rb +120 -0
  107. data/lib/rubino/boot/encryption_key.rb +32 -0
  108. data/lib/rubino/cli/chat/bang_shell.rb +257 -0
  109. data/lib/rubino/cli/chat/completion_builder.rb +290 -0
  110. data/lib/rubino/cli/chat/idle_card_host.rb +69 -0
  111. data/lib/rubino/cli/chat/image_inbox.rb +168 -0
  112. data/lib/rubino/cli/chat/session_resolver.rb +176 -0
  113. data/lib/rubino/cli/chat_command.rb +1674 -0
  114. data/lib/rubino/cli/commands.rb +250 -0
  115. data/lib/rubino/cli/config_command.rb +96 -0
  116. data/lib/rubino/cli/doctor_command.rb +251 -0
  117. data/lib/rubino/cli/jobs_command.rb +60 -0
  118. data/lib/rubino/cli/memory_command.rb +135 -0
  119. data/lib/rubino/cli/onboarding_wizard.rb +207 -0
  120. data/lib/rubino/cli/server_command.rb +139 -0
  121. data/lib/rubino/cli/session_command.rb +125 -0
  122. data/lib/rubino/cli/setup_command.rb +107 -0
  123. data/lib/rubino/cli/skills_command.rb +85 -0
  124. data/lib/rubino/cli/tools_command.rb +81 -0
  125. data/lib/rubino/cli/trust_gate.rb +71 -0
  126. data/lib/rubino/commands/built_ins.rb +46 -0
  127. data/lib/rubino/commands/command.rb +116 -0
  128. data/lib/rubino/commands/executor.rb +550 -0
  129. data/lib/rubino/commands/handlers/agents.rb +510 -0
  130. data/lib/rubino/commands/handlers/config.rb +88 -0
  131. data/lib/rubino/commands/handlers/help.rb +148 -0
  132. data/lib/rubino/commands/handlers/jobs.rb +71 -0
  133. data/lib/rubino/commands/handlers/mcp.rb +229 -0
  134. data/lib/rubino/commands/handlers/memory.rb +200 -0
  135. data/lib/rubino/commands/handlers/sessions.rb +207 -0
  136. data/lib/rubino/commands/handlers/skills.rb +195 -0
  137. data/lib/rubino/commands/handlers/status.rb +211 -0
  138. data/lib/rubino/commands/loader.rb +90 -0
  139. data/lib/rubino/config/configuration.rb +455 -0
  140. data/lib/rubino/config/defaults.rb +569 -0
  141. data/lib/rubino/config/loader.rb +115 -0
  142. data/lib/rubino/config/reasoning_prefs.rb +67 -0
  143. data/lib/rubino/config/writer.rb +72 -0
  144. data/lib/rubino/context/compressor.rb +149 -0
  145. data/lib/rubino/context/environment_inspector.rb +176 -0
  146. data/lib/rubino/context/file_discovery.rb +45 -0
  147. data/lib/rubino/context/message_boundary.rb +39 -0
  148. data/lib/rubino/context/prompt_assembler.rb +382 -0
  149. data/lib/rubino/context/summary_builder.rb +159 -0
  150. data/lib/rubino/context/token_budget.rb +68 -0
  151. data/lib/rubino/context/tool_pair_sanitizer.rb +70 -0
  152. data/lib/rubino/database/connection.rb +77 -0
  153. data/lib/rubino/database/migrations/001_create_initial_schema.rb +156 -0
  154. data/lib/rubino/database/migrations/002_create_runs.rb +45 -0
  155. data/lib/rubino/database/migrations/003_create_skill_states.rb +15 -0
  156. data/lib/rubino/database/migrations/004_create_cron_jobs.rb +36 -0
  157. data/lib/rubino/database/migrations/005_create_oauth_connections.rb +27 -0
  158. data/lib/rubino/database/migrations/006_create_webhook_deliveries.rb +34 -0
  159. data/lib/rubino/database/migrations/007_create_messages_fts.rb +59 -0
  160. data/lib/rubino/database/migrations/008_create_memory_facts.rb +75 -0
  161. data/lib/rubino/database/migrations/009_create_memory_graph.rb +55 -0
  162. data/lib/rubino/database/migrations/010_add_owner_pid_to_sessions.rb +20 -0
  163. data/lib/rubino/database/migrator.rb +48 -0
  164. data/lib/rubino/documents/converters/csv.rb +79 -0
  165. data/lib/rubino/documents/converters/docx.rb +129 -0
  166. data/lib/rubino/documents/converters/html.rb +28 -0
  167. data/lib/rubino/documents/converters/json.rb +35 -0
  168. data/lib/rubino/documents/converters/pdf.rb +59 -0
  169. data/lib/rubino/documents/converters/plain.rb +68 -0
  170. data/lib/rubino/documents/converters/pptx.rb +64 -0
  171. data/lib/rubino/documents/converters/xlsx.rb +62 -0
  172. data/lib/rubino/documents/converters/xml.rb +45 -0
  173. data/lib/rubino/documents/html.rb +71 -0
  174. data/lib/rubino/documents/registry.rb +68 -0
  175. data/lib/rubino/documents/table.rb +63 -0
  176. data/lib/rubino/documents.rb +50 -0
  177. data/lib/rubino/errors.rb +119 -0
  178. data/lib/rubino/files/workspace.rb +93 -0
  179. data/lib/rubino/interaction/cancel_token.rb +43 -0
  180. data/lib/rubino/interaction/clipboard_image.rb +84 -0
  181. data/lib/rubino/interaction/event_bus.rb +48 -0
  182. data/lib/rubino/interaction/events.rb +101 -0
  183. data/lib/rubino/interaction/image_input.rb +127 -0
  184. data/lib/rubino/interaction/input_queue.rb +117 -0
  185. data/lib/rubino/interaction/lifecycle.rb +299 -0
  186. data/lib/rubino/interaction/probe.rb +65 -0
  187. data/lib/rubino/interaction/state.rb +56 -0
  188. data/lib/rubino/jobs/cron_job_repository.rb +75 -0
  189. data/lib/rubino/jobs/handlers/cleanup_sessions_job.rb +32 -0
  190. data/lib/rubino/jobs/handlers/compact_session_job.rb +21 -0
  191. data/lib/rubino/jobs/handlers/distill_skill_job.rb +186 -0
  192. data/lib/rubino/jobs/handlers/extract_memory_job.rb +37 -0
  193. data/lib/rubino/jobs/handlers/summarize_session_job.rb +21 -0
  194. data/lib/rubino/jobs/queue.rb +184 -0
  195. data/lib/rubino/jobs/registry.rb +45 -0
  196. data/lib/rubino/jobs/runner.rb +79 -0
  197. data/lib/rubino/jobs/scheduler.rb +138 -0
  198. data/lib/rubino/jobs/webhook_delivery.rb +225 -0
  199. data/lib/rubino/jobs/worker.rb +59 -0
  200. data/lib/rubino/llm/adapter_factory.rb +47 -0
  201. data/lib/rubino/llm/adapter_response.rb +65 -0
  202. data/lib/rubino/llm/auxiliary_client.rb +61 -0
  203. data/lib/rubino/llm/bedrock_bearer_client.rb +235 -0
  204. data/lib/rubino/llm/content_builder.rb +55 -0
  205. data/lib/rubino/llm/credential_check.rb +93 -0
  206. data/lib/rubino/llm/error_classifier.rb +364 -0
  207. data/lib/rubino/llm/fake_provider.rb +292 -0
  208. data/lib/rubino/llm/inline_think_filter.rb +58 -0
  209. data/lib/rubino/llm/model_catalog.rb +29 -0
  210. data/lib/rubino/llm/provider_resolver.rb +48 -0
  211. data/lib/rubino/llm/reasoning_manager.rb +100 -0
  212. data/lib/rubino/llm/request.rb +56 -0
  213. data/lib/rubino/llm/ruby_llm_adapter.rb +794 -0
  214. data/lib/rubino/llm/scenario_loader.rb +68 -0
  215. data/lib/rubino/llm/scenario_selector.rb +80 -0
  216. data/lib/rubino/llm/scenarios/agent-creates-cron-failure.yml +29 -0
  217. data/lib/rubino/llm/scenarios/agent-creates-cron.yml +36 -0
  218. data/lib/rubino/llm/scenarios/analysis.yml +501 -0
  219. data/lib/rubino/llm/scenarios/complex-analysis.yml +598 -0
  220. data/lib/rubino/llm/scenarios/failure.yml +65 -0
  221. data/lib/rubino/llm/scenarios/happy-path.yml +24 -0
  222. data/lib/rubino/llm/scenarios/provider-quota-completed.yml +14 -0
  223. data/lib/rubino/llm/scenarios/wide-table.yml +121 -0
  224. data/lib/rubino/llm/scenarios/with-approvals.yml +50 -0
  225. data/lib/rubino/llm/scenarios/with-artifacts.yml +98 -0
  226. data/lib/rubino/llm/scenarios/with-clarify.yml +32 -0
  227. data/lib/rubino/llm/scenarios/with-reasoning.yml +175 -0
  228. data/lib/rubino/llm/scenarios/with-uploads.yml +104 -0
  229. data/lib/rubino/llm/thinking_support.rb +84 -0
  230. data/lib/rubino/llm/tool_bridge.rb +89 -0
  231. data/lib/rubino/logger.rb +99 -0
  232. data/lib/rubino/mcp/manager.rb +180 -0
  233. data/lib/rubino/mcp/mcp_tool_wrapper.rb +69 -0
  234. data/lib/rubino/mcp.rb +57 -0
  235. data/lib/rubino/memory/backend.rb +104 -0
  236. data/lib/rubino/memory/backends/default.rb +101 -0
  237. data/lib/rubino/memory/backends/sqlite.rb +653 -0
  238. data/lib/rubino/memory/backends.rb +53 -0
  239. data/lib/rubino/memory/deduplicator.rb +74 -0
  240. data/lib/rubino/memory/extractor.rb +85 -0
  241. data/lib/rubino/memory/flusher.rb +31 -0
  242. data/lib/rubino/memory/retriever.rb +50 -0
  243. data/lib/rubino/memory/sqlite_extraction_prompt.rb +70 -0
  244. data/lib/rubino/memory/sqlite_graph.rb +154 -0
  245. data/lib/rubino/memory/store.rb +228 -0
  246. data/lib/rubino/memory/threat_scanner.rb +68 -0
  247. data/lib/rubino/metrics.rb +175 -0
  248. data/lib/rubino/modes.rb +93 -0
  249. data/lib/rubino/oauth/connection_repository.rb +95 -0
  250. data/lib/rubino/oauth/provider/github.rb +75 -0
  251. data/lib/rubino/oauth/provider/google.rb +59 -0
  252. data/lib/rubino/oauth/provider.rb +149 -0
  253. data/lib/rubino/oauth/registry.rb +86 -0
  254. data/lib/rubino/oauth/token_encryptor.rb +87 -0
  255. data/lib/rubino/plugins/registry.rb +75 -0
  256. data/lib/rubino/plugins.rb +86 -0
  257. data/lib/rubino/run/approval_gate.rb +243 -0
  258. data/lib/rubino/run/attachment_downloader.rb +166 -0
  259. data/lib/rubino/run/event_store.rb +74 -0
  260. data/lib/rubino/run/executor.rb +383 -0
  261. data/lib/rubino/run/gate_registry.rb +39 -0
  262. data/lib/rubino/run/recorder.rb +69 -0
  263. data/lib/rubino/run/repository.rb +118 -0
  264. data/lib/rubino/run/session_approval_cache.rb +118 -0
  265. data/lib/rubino/security/allowlist_persister.rb +55 -0
  266. data/lib/rubino/security/approval_policy.rb +227 -0
  267. data/lib/rubino/security/command_allowlist.rb +24 -0
  268. data/lib/rubino/security/dangerous_patterns.rb +118 -0
  269. data/lib/rubino/security/deny_persister.rb +73 -0
  270. data/lib/rubino/security/doom_loop_detector.rb +43 -0
  271. data/lib/rubino/security/hardline_guard.rb +105 -0
  272. data/lib/rubino/security/pattern_matcher.rb +62 -0
  273. data/lib/rubino/security/prefix_deriver.rb +124 -0
  274. data/lib/rubino/security/readonly_commands.rb +211 -0
  275. data/lib/rubino/session/exporter.rb +101 -0
  276. data/lib/rubino/session/message.rb +77 -0
  277. data/lib/rubino/session/repository.rb +295 -0
  278. data/lib/rubino/session/store.rb +198 -0
  279. data/lib/rubino/session/summary_store.rb +65 -0
  280. data/lib/rubino/skills/prompt_index.rb +85 -0
  281. data/lib/rubino/skills/registry.rb +208 -0
  282. data/lib/rubino/skills/skill.rb +176 -0
  283. data/lib/rubino/skills/skill_tool.rb +215 -0
  284. data/lib/rubino/skills/state_repository.rb +37 -0
  285. data/lib/rubino/skills/toggle.rb +26 -0
  286. data/lib/rubino/tools/answer_child_tool.rb +83 -0
  287. data/lib/rubino/tools/ask_parent_tool.rb +232 -0
  288. data/lib/rubino/tools/attach_file_tool.rb +120 -0
  289. data/lib/rubino/tools/background_tasks.rb +520 -0
  290. data/lib/rubino/tools/base.rb +222 -0
  291. data/lib/rubino/tools/custom_tool_loader.rb +119 -0
  292. data/lib/rubino/tools/edit_tool.rb +122 -0
  293. data/lib/rubino/tools/git_tool.rb +71 -0
  294. data/lib/rubino/tools/github_tool.rb +233 -0
  295. data/lib/rubino/tools/glob_tool.rb +69 -0
  296. data/lib/rubino/tools/grep_tool.rb +206 -0
  297. data/lib/rubino/tools/memory_tool.rb +184 -0
  298. data/lib/rubino/tools/multi_edit_tool.rb +110 -0
  299. data/lib/rubino/tools/patch_tool.rb +260 -0
  300. data/lib/rubino/tools/probe_tool.rb +175 -0
  301. data/lib/rubino/tools/question_tool.rb +128 -0
  302. data/lib/rubino/tools/read_attachment_tool.rb +180 -0
  303. data/lib/rubino/tools/read_tool.rb +212 -0
  304. data/lib/rubino/tools/read_tracker.rb +98 -0
  305. data/lib/rubino/tools/registry.rb +166 -0
  306. data/lib/rubino/tools/result.rb +113 -0
  307. data/lib/rubino/tools/ruby_tool.rb +0 -0
  308. data/lib/rubino/tools/session_search_tool.rb +103 -0
  309. data/lib/rubino/tools/shell_input_tool.rb +96 -0
  310. data/lib/rubino/tools/shell_kill_tool.rb +76 -0
  311. data/lib/rubino/tools/shell_output_tool.rb +72 -0
  312. data/lib/rubino/tools/shell_registry.rb +158 -0
  313. data/lib/rubino/tools/shell_tail_tool.rb +118 -0
  314. data/lib/rubino/tools/shell_tool.rb +330 -0
  315. data/lib/rubino/tools/steer_tool.rb +118 -0
  316. data/lib/rubino/tools/subagent_probe.rb +89 -0
  317. data/lib/rubino/tools/summarize_file_tool.rb +182 -0
  318. data/lib/rubino/tools/task_result_tool.rb +90 -0
  319. data/lib/rubino/tools/task_stop_tool.rb +80 -0
  320. data/lib/rubino/tools/task_tool.rb +622 -0
  321. data/lib/rubino/tools/test_tool.rb +454 -0
  322. data/lib/rubino/tools/todo_tool.rb +93 -0
  323. data/lib/rubino/tools/tool_call_repository.rb +33 -0
  324. data/lib/rubino/tools/vision_tool.rb +85 -0
  325. data/lib/rubino/tools/webfetch_tool.rb +153 -0
  326. data/lib/rubino/tools/websearch_tool.rb +179 -0
  327. data/lib/rubino/tools/write_tool.rb +61 -0
  328. data/lib/rubino/trust.rb +88 -0
  329. data/lib/rubino/ui/api.rb +296 -0
  330. data/lib/rubino/ui/base.rb +252 -0
  331. data/lib/rubino/ui/bottom_composer.rb +1599 -0
  332. data/lib/rubino/ui/cli.rb +1987 -0
  333. data/lib/rubino/ui/completion_menu.rb +321 -0
  334. data/lib/rubino/ui/completion_source.rb +284 -0
  335. data/lib/rubino/ui/escape_reader.rb +169 -0
  336. data/lib/rubino/ui/indented_io.rb +88 -0
  337. data/lib/rubino/ui/input_history.rb +108 -0
  338. data/lib/rubino/ui/live_region.rb +183 -0
  339. data/lib/rubino/ui/markdown_renderer.rb +506 -0
  340. data/lib/rubino/ui/notifier.rb +163 -0
  341. data/lib/rubino/ui/null.rb +195 -0
  342. data/lib/rubino/ui/paste_store.rb +176 -0
  343. data/lib/rubino/ui/printer_base.rb +79 -0
  344. data/lib/rubino/ui/probe_wait_indicator.rb +75 -0
  345. data/lib/rubino/ui/queued_indicators.rb +66 -0
  346. data/lib/rubino/ui/status_bar.rb +100 -0
  347. data/lib/rubino/ui/stdout_proxy.rb +161 -0
  348. data/lib/rubino/ui/streaming_markdown.rb +186 -0
  349. data/lib/rubino/ui/subagent_cards.rb +134 -0
  350. data/lib/rubino/ui/subagent_view.rb +255 -0
  351. data/lib/rubino/ui.rb +21 -0
  352. data/lib/rubino/update_check.rb +187 -0
  353. data/lib/rubino/util/duration.rb +23 -0
  354. data/lib/rubino/util/hyperlink.rb +105 -0
  355. data/lib/rubino/util/output.rb +145 -0
  356. data/lib/rubino/util/secrets_mask.rb +83 -0
  357. data/lib/rubino/version.rb +5 -0
  358. data/lib/rubino/workspace.rb +85 -0
  359. data/lib/rubino-agent.rb +5 -0
  360. data/lib/rubino.rb +318 -0
  361. data/mise.toml +2 -0
  362. data/rubino-agent.gemspec +103 -0
  363. data/skills/ruby-expert/SKILL.md +67 -0
  364. data/skills/ruby-expert/references/concurrency.md +357 -0
  365. data/skills/ruby-expert/references/datetime-and-encoding.md +363 -0
  366. data/skills/ruby-expert/references/errors-and-types.md +460 -0
  367. data/skills/ruby-expert/references/gem-authoring.md +459 -0
  368. data/skills/ruby-expert/references/language-idioms.md +465 -0
  369. data/skills/ruby-expert/references/metaprogramming.md +339 -0
  370. data/skills/ruby-expert/references/oo-design.md +553 -0
  371. data/skills/ruby-expert/references/performance.md +383 -0
  372. data/skills/ruby-expert/references/rails.md +424 -0
  373. data/skills/ruby-expert/references/security.md +404 -0
  374. data/skills/ruby-expert/references/testing.md +473 -0
  375. data/skills/ruby-expert/references/tooling.md +466 -0
  376. metadata +856 -0
data/docs/tools.md ADDED
@@ -0,0 +1,395 @@
1
+ # Tools Reference
2
+
3
+ rubino ships **34 built-in tools** plus dynamic MCP tools (started at boot when `mcp.servers` is configured — see [mcp.md](mcp.md); being server-dependent they are excluded from the drift-checked list below) and custom user-defined tools. Each tool is gated by a `tools.<key>` config flag (opt-out: absent key = enabled, only an explicit `false` disables) and the approval model. The count and list below are drift-checked against the live registry by `spec/docs/tools_doc_drift_spec.rb`.
4
+
5
+ The full list (registration order): `read`, `summarize_file`, `write`, `edit`, `multi_edit`, `grep`, `glob`, `git`, `github`, `shell`, `shell_output`, `shell_tail`, `shell_input`, `shell_kill`, `ruby`, `run_tests`, `apply_patch`, `webfetch`, `websearch`, `question`, `todowrite`, `memory`, `session_search`, `attach_file`, `read_attachment`, `vision`, `skill`, `task`, `task_result`, `task_stop`, `ask_parent`, `steer`, `probe`, `answer_child`.
6
+
7
+ Several tools share one config gate, so `rubino tools` shows **27 rows** (config groups), not 34: `webfetch` + `websearch` share `tools.web`, and the whole delegation family (`task`, `task_result`, `task_stop`, `ask_parent`, `steer`, `probe`, `answer_child`) rides on `tools.task` — disabling delegation disables them all.
8
+
9
+ ## How tools are gated
10
+
11
+ - **Config flag** — `tools.<config_key>`. Most tools key on their own name; `webfetch`/`websearch` share `tools.web`; the delegation family shares `tools.task`; absent keys default to enabled. `rubino tools` prints the effective state per config group.
12
+ - **Mode** — `plan` mode pares the registry down to read-only tools (no `edit`/`shell`/`git`/…); `default` and `yolo` expose everything (their difference is on the approval path).
13
+ - **Approval** — see [security.md](security.md). Shell commands are confirmation-gated by default; a non-bypassable hardline floor blocks catastrophic commands regardless of mode.
14
+ - **Workspace sandbox** — with `tools.workspace_strict: true` (default), write/edit/delete tools are confined to the workspace root (`terminal.cwd` or `Dir.pwd`).
15
+
16
+ ## Built-in Tools
17
+
18
+ ### read
19
+
20
+ Read a text file from the filesystem with line numbers (cat -n style). Long lines are truncated; the default window is the first chunk of lines.
21
+
22
+ ```
23
+ Risk: low
24
+ Parameters: file_path, offset, limit
25
+ ```
26
+
27
+ ### summarize_file
28
+
29
+ Summarize a large text file WITHOUT loading it into the conversation. The file is map-reduced by a separate summarization model; only the final summary returns, so the raw bytes never enter context. Prefer this over `read` for big documents.
30
+
31
+ ```
32
+ Risk: low
33
+ Parameters: file_path, focus, max_words
34
+ ```
35
+
36
+ ### write
37
+
38
+ Write content to a file, overwriting any existing content. Creates parent directories if needed. Use `edit`/`multi_edit` to modify an existing file in place.
39
+
40
+ ```
41
+ Risk: medium
42
+ Parameters: file_path, content
43
+ ```
44
+
45
+ ### edit
46
+
47
+ Exact string replacement in a file. The old text must match exactly (including whitespace). More precise than full file writes.
48
+
49
+ ```
50
+ Risk: medium
51
+ Parameters: file_path, old_string, new_string, replace_all
52
+ ```
53
+
54
+ ### multi_edit
55
+
56
+ Apply multiple exact string replacements to a single file atomically. Edits apply sequentially; if any edit fails, no changes are written.
57
+
58
+ ```
59
+ Risk: medium
60
+ Parameters: file_path, edits[], replace_all
61
+ ```
62
+
63
+ ### grep
64
+
65
+ Regex content search. Uses ripgrep (rg) if available, falls back to Ruby.
66
+
67
+ ```
68
+ Risk: low
69
+ Parameters: pattern, path, include, max_results
70
+ ```
71
+
72
+ ### glob
73
+
74
+ Find files by glob pattern. Returns paths sorted by modification time.
75
+
76
+ ```
77
+ Risk: low
78
+ Parameters: pattern, path, max_results
79
+ ```
80
+
81
+ ### git
82
+
83
+ Git operations: status, diff, log, branch, show.
84
+
85
+ ```
86
+ Risk: low (read-only operations)
87
+ Parameters: command, args
88
+ ```
89
+
90
+ ### github
91
+
92
+ GitHub integration: PRs, issues, reviews. Uses gh CLI or REST API.
93
+
94
+ ```
95
+ Risk: medium
96
+ Parameters: action, title, body, number, repo, base, labels
97
+ Actions: pr_create, pr_list, pr_view, pr_checks, pr_diff, issue_create, issue_list, issue_view, repo_view, release_list
98
+ ```
99
+
100
+ ### shell
101
+
102
+ Execute a shell command. Foreground blocks until exit or `timeout`; pass `run_in_background: true` to fire-and-forget and get a `run_id`.
103
+
104
+ Commands run under `bash -o pipefail` (foreground and background), so a failure in the **middle** of a pipeline surfaces as the pipeline's exit code instead of being masked by an innocuous last stage. One consequence: an early-closing consumer (`cmd | head -1`) makes the upstream stage exit 141 (128+SIGPIPE); the tool reports the honest exit code with a SIGPIPE note but treats it as success.
105
+
106
+ Provably read-only commands (`ls`, `grep`, `git log`, ...) run without an approval prompt by default — see [Auto-allowed read-only commands](security.md#auto-allowed-read-only-commands).
107
+
108
+ ```
109
+ Risk: high (always requires approval unless in allowlist or provably read-only)
110
+ Parameters: command, cwd, timeout, run_in_background
111
+ ```
112
+
113
+ ### shell_output
114
+
115
+ Read output from a background shell started via `shell` with `run_in_background: true`. Returns only new bytes by default; pass `mode: "all"` for the full buffer.
116
+
117
+ ```
118
+ Risk: low
119
+ Parameters: run_id, mode
120
+ ```
121
+
122
+ ### shell_tail
123
+
124
+ Follow a background shell — block until new bytes arrive on its `run_id`, the process exits, or `timeout` elapses. Use for `tail -F`-style following.
125
+
126
+ ```
127
+ Risk: low
128
+ Parameters: run_id, timeout
129
+ ```
130
+
131
+ ### shell_input
132
+
133
+ Send a line of input to the stdin of a running background shell (e.g. answer an interactive prompt) addressed by `run_id`.
134
+
135
+ ```
136
+ Risk: medium
137
+ Parameters: run_id, input
138
+ ```
139
+
140
+ ### shell_kill
141
+
142
+ Terminate a background shell started via `shell`. Sends SIGTERM to the process group, then SIGKILL if still alive.
143
+
144
+ ```
145
+ Risk: medium
146
+ Parameters: run_id
147
+ ```
148
+
149
+ ### ruby
150
+
151
+ Evaluate Ruby code and return the result. The snippet runs in a **separate Ruby process rooted at the workspace**, with the project's `lib/` and the workspace root prepended to `$LOAD_PATH` (like `ruby -Ilib -I. -e ...`) — so `require 'my_project/file'` and relative requires of the code being worked on resolve. A child process also keeps the snippet from crashing or polluting the host agent (it can `exit`, redefine constants, leak globals). (issue #102)
152
+
153
+ ```
154
+ Risk: medium
155
+ Parameters: code
156
+ ```
157
+
158
+ ### run_tests
159
+
160
+ Run the workspace project's test suite and return a **structured** result instead of the raw toolchain firehose. Auto-detects RSpec / Minitest / a Rakefile default task, prefers `bundle exec` when a Gemfile is present (falls back to the bare runner if the bundle is broken), and returns pass/fail counts plus the failing examples (name + file:line + short message) and a short raw tail. Distinguishes "the suite couldn't start" (toolchain error) from "the suite ran and N failed". Use this instead of driving `shell` by hand to run tests. (issue #101)
161
+
162
+ ```
163
+ Risk: low
164
+ Parameters: path (optional file/pattern), framework (optional: rspec|minitest|rake)
165
+ ```
166
+
167
+ ### apply_patch
168
+
169
+ Apply unified diff patches to files.
170
+
171
+ ```
172
+ Risk: medium
173
+ Parameters: patch, base_path
174
+ ```
175
+
176
+ ### webfetch
177
+
178
+ Fetch web page content and return as text.
179
+
180
+ ```
181
+ Risk: low
182
+ Parameters: url, format (text|html)
183
+ ```
184
+
185
+ ### websearch
186
+
187
+ Search the web. Supports Tavily (best), SearXNG, or DuckDuckGo fallback.
188
+
189
+ ```
190
+ Risk: low
191
+ Parameters: query, max_results
192
+ Env: TAVILY_API_KEY or SEARXNG_URL (optional)
193
+ ```
194
+
195
+ ### question
196
+
197
+ Ask the user a question with optional predefined choices.
198
+
199
+ ```
200
+ Risk: low
201
+ Parameters: question, options[], multiple
202
+ ```
203
+
204
+ Non-interactive / no-TTY behavior: the tool fails closed. When there is no
205
+ interactive terminal to prompt on — a piped or redirected `rubino prompt`
206
+ (stdin or stdout not a TTY), a subagent context, or an API/server run with no
207
+ pending clarify gate — nothing is prompted and no terminal escape sequences
208
+ are emitted. The tool immediately returns a deterministic structured result
209
+ ("No answer: no interactive user input available …") instructing the model
210
+ not to assume a choice on the user's behalf. It never reads ambient stdin and
211
+ never silently auto-selects an option. On the HTTP API path with a clarify
212
+ gate wired, the question is still delivered as a `clarify.required` event and
213
+ the tool waits for the client's answer as before.
214
+
215
+ ### todowrite
216
+
217
+ Track tasks during a session.
218
+
219
+ ```
220
+ Risk: low
221
+ Parameters: todos[] (content, status, priority)
222
+ ```
223
+
224
+ ### memory
225
+
226
+ Persist facts across sessions. `action=add` records a new fact, `replace` updates an existing one, `remove` deletes one. `target=user` writes the user profile; `target=memory` writes general memory. Content is scanned for prompt-injection / exfiltration patterns and subject to a character budget.
227
+
228
+ ```
229
+ Risk: medium
230
+ Parameters: action, target, content, old_text
231
+ ```
232
+
233
+ ### session_search
234
+
235
+ Full-text search across past session messages. Returns matched messages with highlighted snippets and the owning session id.
236
+
237
+ ```
238
+ Risk: low
239
+ Parameters: query, since, until, role, tool, limit
240
+ ```
241
+
242
+ ### attach_file
243
+
244
+ Attach a previously-written file to the current turn as a downloadable artifact for the user. Call AFTER creating the file with `write`/`edit`/`shell`. Does not copy or move the file — only registers it as a deliverable.
245
+
246
+ ```
247
+ Risk: low
248
+ Parameters: file_path, filename
249
+ ```
250
+
251
+ ### read_attachment
252
+
253
+ Read an attached document on demand, converting it to Markdown **in-process** (PDF, DOCX, XLSX, PPTX, HTML, CSV, JSON, XML, plain/code) and returning the text framed as untrusted user data (nonce-delimited, defanged). Prefer this over shelling out to `markitdown`/`pdftotext`. The path is classified by the fail-closed attachment safety pipeline (regular-file check, workspace confine, size cap, magic-bytes MIME) before any conversion. Oversized documents are routed through the `summarize` auxiliary model instead of flooding the conversation; if the format has no in-process converter (its optional extraction gem isn't installed), an actionable shell-extraction hint is returned instead of raising. The conversion is provided by the in-repo `Rubino::Documents` module; its CORE converters lean on optional MIT gems (`roo`, `docx`, `pdf-reader`, `ruby_powerpoint`) that are lazily required — none is a hard dependency, and `rubino doctor` reports which formats are available in-process.
254
+
255
+ ```
256
+ Risk: low
257
+ Parameters: file_path, summarize, focus
258
+ ```
259
+
260
+ ### vision
261
+
262
+ Ask a multimodal model to describe or interpret an image (charts, screenshots, diagrams, photos). Provide an optional focused question. Hidden only when no auxiliary vision model is configured and the primary model cannot see.
263
+
264
+ ```
265
+ Risk: low
266
+ Parameters: file_path, question
267
+ ```
268
+
269
+ ### skill
270
+
271
+ Load a skill body (Level 2) and any bundled files (Level 3) on demand, or create a new skill (`action: "create"`). The agent sees available skills (name + description) up front and calls this to pull in the full instructions only when relevant. After a complex, repeatable task it can also distil what it did into a new skill — and the deterministic post-turn `DistillSkillJob` does this automatically. Gated by `tools.skill`. See **[docs/skills.md](skills.md)** for the skill system — the 3-level disclosure, creating skills (the post-turn job + the on-demand tool), authoring `SKILL.md` files, and the `SKILL_LOADED` / `SKILL_CREATED` observability signals.
272
+
273
+ ```
274
+ Risk: low
275
+ Parameters: action, name, file_path, description, body
276
+ ```
277
+
278
+ ### task
279
+
280
+ Delegate a sub-task to an isolated subagent run (default: a background subagent that returns a task id immediately; `background: false` runs it inline). Gated by `tools.task`. Subagents keep the `task` tool, so they CAN spawn their own subagents — scoped nesting, bounded by three caps enforced in one place (`BackgroundTasks#reserve`): `tasks.max_depth` (default 2), `tasks.max_children_per_node` (default 3), and `tasks.max_concurrent_total` (default 8). See [agents.md](agents.md).
281
+
282
+ ```
283
+ Risk: low (the nested run's tools carry their own approval/risk gates)
284
+ Parameters: subagent, prompt, (background)
285
+ ```
286
+
287
+ ### task_result
288
+
289
+ Poll a background subagent for its output (companion to `task`, mirrors `shell_output`). Gated by `tools.task`.
290
+
291
+ ```
292
+ Risk: low
293
+ Parameters: task_id
294
+ ```
295
+
296
+ ### task_stop
297
+
298
+ Stop a running background subagent (companion to `task`, mirrors `shell_kill`). Gated by `tools.task`.
299
+
300
+ ```
301
+ Risk: medium
302
+ Parameters: task_id
303
+ ```
304
+
305
+ ### ask_parent
306
+
307
+ Child→parent escalation: a subagent asks its parent a question it cannot resolve from its sealed prompt. `blocking: true` pauses the child until the answer arrives; `blocking: false` (default) lets it keep working and folds the answer in later as a note. The parent (agent or human) answers via `answer_child` / `/reply`. Only available to subagents — a top-level agent has no parent to ask. Gated by `tools.task`.
308
+
309
+ ```
310
+ Risk: low
311
+ Parameters: question, blocking
312
+ ```
313
+
314
+ ### steer
315
+
316
+ Parent→child steering note: park a short note on one of YOUR OWN running subagents; it is folded into the child's context at its next turn boundary and persists (it changes the child's trajectory). Ownership-scoped at call time — only your direct children. The model counterpart of the human `/agents <id> steer "…"`. Gated by `tools.task`.
317
+
318
+ ```
319
+ Risk: low
320
+ Parameters: task_id, note
321
+ ```
322
+
323
+ ### probe
324
+
325
+ Parent→child ephemeral peek: check on one of YOUR OWN running subagents without disturbing it (read-only — nothing is saved to the child). `live: false` (default) returns a free registry snapshot (status, tool count, last activity, recent lines); `live: true` runs a billed one-shot model peek over the child's transcript, budgeted per child (`tasks.max_live_probes_per_child`, default 5). The model counterpart of the human `/agents <id> probe "…"`. Gated by `tools.task`.
326
+
327
+ ```
328
+ Risk: low
329
+ Parameters: task_id, question, live
330
+ ```
331
+
332
+ ### answer_child
333
+
334
+ Parent→child answer to an `ask_parent` question: delivers the answer into the asking child's context (unblocks a blocking ask; folds in for a non-blocking one). Ownership-scoped — only a direct child that is actually waiting. The model counterpart of the human `/reply <id> <answer>`. Gated by `tools.task`.
335
+
336
+ ```
337
+ Risk: low
338
+ Parameters: task_id, answer
339
+ ```
340
+
341
+ ---
342
+
343
+ ## MCP Tools
344
+
345
+ Tools from connected MCP servers are automatically registered with a prefix:
346
+
347
+ ```
348
+ server_name_tool_name
349
+ ```
350
+
351
+ Configure MCP servers in `config.yml`:
352
+
353
+ ```yaml
354
+ mcp:
355
+ servers:
356
+ myserver:
357
+ transport: stdio
358
+ command: "npx"
359
+ args: ["my-mcp-server"]
360
+ ```
361
+
362
+ ---
363
+
364
+ ## Custom Tools
365
+
366
+ Create Ruby files in `.rubino/tools/`:
367
+
368
+ ```ruby
369
+ # .rubino/tools/deploy.rb
370
+ Rubino.define_tool do
371
+ name "deploy"
372
+ description "Deploy the application to staging or production"
373
+
374
+ input_schema({
375
+ type: "object",
376
+ properties: {
377
+ environment: { type: "string", enum: ["staging", "production"] }
378
+ },
379
+ required: ["environment"]
380
+ })
381
+
382
+ risk_level :high
383
+
384
+ execute do |args|
385
+ env = args["environment"]
386
+ `./deploy.sh #{env} 2>&1`
387
+ end
388
+ end
389
+ ```
390
+
391
+ Custom tools:
392
+ - Are automatically discovered and registered
393
+ - Can override built-in tools by name
394
+ - Support all risk levels and approval flows
395
+ - Can execute any system command or Ruby code
@@ -0,0 +1,73 @@
1
+ # Troubleshooting
2
+
3
+ Keyed on the exact strings you'll see. Run `rubino doctor` first — it reports config, the resolved provider, credential presence, and database health.
4
+
5
+ ## `No API key configured for provider '...' (model ...)`
6
+
7
+ The resolved provider has no usable credential, so the run failed fast (instead of the old ~80s silent-retry-then-empty dead end, #93).
8
+
9
+ Fix one of:
10
+
11
+ - `rubino setup` — guided first-run; pick a provider and paste a key.
12
+ - add `<PROVIDER>_API_KEY=<key>` to `~/.rubino/.env`.
13
+ - set `providers.<provider>.api_key` in `~/.rubino/config.yml`.
14
+
15
+ See [models-and-keys.md](models-and-keys.md).
16
+
17
+ ## First message hung ~80s then exited empty (older versions)
18
+
19
+ The historical default→OpenRouter trap: the shipped `model.default` `openai/gpt-4.1` resolves to **OpenRouter** in ruby_llm's registry, and a missing key produced a silent retry storm and an empty success. Current versions fail fast with the message above (interactively, they launch the onboarding wizard). If you still see a hang, you're on an old build — upgrade, or run `rubino setup`.
20
+
21
+ ## OpenRouter is mentioned but I never chose it
22
+
23
+ Same root cause: `openai/gpt-4.1` is an OpenRouter-namespaced id. To use OpenAI's own API, set a bare model id with an explicit provider:
24
+
25
+ ```yaml
26
+ model:
27
+ default: "gpt-4.1"
28
+ provider: "openai"
29
+ ```
30
+
31
+ Or run `rubino setup` and pick a provider explicitly. See [models-and-keys.md](models-and-keys.md#the-defaultopenrouter-trap-refs-93).
32
+
33
+ ## `rubino isn't set up yet — run \`rubino setup\` first.`
34
+
35
+ The database couldn't be auto-initialized. `chat` auto-creates the home dirs and runs migrations on boot, so this only appears when that itself fails (e.g. unwritable home). Check that `~/.rubino` (or `$RUBINO_HOME`) is writable, then run `rubino setup`.
36
+
37
+ ## `fake provider is dev-only — set RUBINO_ALLOW_FAKE=1 to opt in.`
38
+
39
+ Your configured `model.provider` is `fake`. The fake provider can short-circuit tool decisions and is blocked in `chat`/`server` unless you set `RUBINO_ALLOW_FAKE=1`. Either set that env var (dev only) or switch to a real provider.
40
+
41
+ ## `rubino memory list` says no memories but the agent clearly remembers things
42
+
43
+ Fixed in #94. The CLI now reads the **active** backend (`memory.backend`, default `sqlite`) — the same store the agent writes to. If you're on an old build, the CLI read a hardwired legacy table. Confirm the active backend with `rubino memory backend`, and upgrade if it still mismatches.
44
+
45
+ ## Memory recall misses an obvious fact
46
+
47
+ The sqlite backend ranks by direct content relevance (FTS5/BM25) first; graph/recency only backfill. If a fact isn't surfacing, check it's still **live** (`rubino memory list` shows live facts; superseded ones are hidden) and that your probe shares non-stopword terms with the fact. Vector KNN is off by default — enable `memory.sqlite.vector: true` (needs `RubyLLM.embed`) for semantic matches. See [memory.md](memory.md).
48
+
49
+ ## The agent keeps asking to approve every shell command
50
+
51
+ That's `security.require_confirmation_for_shell: true` (the default `confirm_all` policy). Options: approve for the session at the prompt, add prefixes to `security.command_allowlist`, switch to `dangerous_only` (`security.confirm_policy: dangerous_only`), or use `/mode yolo` / `--yolo` to skip prompts (the hardline floor and `permissions: deny` still apply). See [security.md](security.md).
52
+
53
+ ## A command was denied even with `--yolo`
54
+
55
+ The hardline floor and explicit `permissions: deny` rules run **before** the yolo allow-exit by design — yolo trusts the agent to move fast, not to wipe the disk. See the [hardline floor](security.md#the-hardline-floor) for the (tiny) blocked set.
56
+
57
+ ## Connection refused / unauthorized against the API
58
+
59
+ - **Connection refused** — the server binds `127.0.0.1` by default. Use `--host 0.0.0.0` (or `RUBINO_API_HOST`) to expose it, only behind TLS or a trusted segment.
60
+ - **401 unauthorized** — every route except `GET /v1/health` and `GET /v1/metrics` requires `Authorization: Bearer <RUBINO_API_KEY>`. Set `RUBINO_API_KEY` (or `--api_key`) on the server and send the matching token.
61
+ - **TLS errors on a remote HTTP client** — the API serves a self-signed cert; the client must **pin** it. Fetch it with `rubino tls-cert`. See [security.md](security.md#tls-for-the-http-api).
62
+
63
+ ## OAuth routes return errors
64
+
65
+ OAuth token storage needs `RUBINO_ENCRYPTION_KEY` set (e.g. `openssl rand -base64 32`). See [oauth-providers.md](oauth-providers.md).
66
+
67
+ ## Tool shows as disabled in `rubino tools`
68
+
69
+ Tools are opt-out: a `tools.<key>: false` in config disables them. Note both web tools share `tools.web`. `plan` mode also hides mutating tools. Check your config and current mode (`/status`).
70
+
71
+ ## Network calls fail behind a corporate proxy
72
+
73
+ Set the standard `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` variables (SOCKS supported). There is **no** `RUBINO_PROXY_URL`. For a custom CA bundle, set `SSL_CERT_FILE`.
data/exe/rubino ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "rubino"
5
+
6
+ # Load .env before anything else so ENV vars are available immediately
7
+ Rubino::Config::Loader.new.load
8
+
9
+ Rubino::CLI::Commands.start(ARGV)