shared_tools 0.4.4 → 0.7.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 (144) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.md +211 -7
  4. data/lib/shared_tools/apply_patch_tool.rb +4 -0
  5. data/lib/shared_tools/bash_tool.rb +4 -0
  6. data/lib/shared_tools/bundle_tool.rb +4 -0
  7. data/lib/shared_tools/command_guard.rb +35 -0
  8. data/lib/shared_tools/csv_read_tool.rb +4 -0
  9. data/lib/shared_tools/csv_write_tool.rb +4 -0
  10. data/lib/shared_tools/data_path.rb +52 -0
  11. data/lib/shared_tools/diff_tool.rb +4 -0
  12. data/lib/shared_tools/download_file_tool.rb +4 -0
  13. data/lib/shared_tools/gem_tool.rb +4 -0
  14. data/lib/shared_tools/git_tool.rb +5 -0
  15. data/lib/shared_tools/glob_tool.rb +4 -0
  16. data/lib/shared_tools/http_request_tool.rb +4 -0
  17. data/lib/shared_tools/json_query_tool.rb +4 -0
  18. data/lib/shared_tools/lint_tool.rb +4 -0
  19. data/lib/shared_tools/mcp/brave_search_client.rb +18 -16
  20. data/lib/shared_tools/mcp/chart_client.rb +18 -15
  21. data/lib/shared_tools/mcp/client_support.rb +103 -0
  22. data/lib/shared_tools/mcp/github_client.rb +23 -19
  23. data/lib/shared_tools/mcp/hugging_face_client.rb +22 -22
  24. data/lib/shared_tools/mcp/memory_client.rb +20 -15
  25. data/lib/shared_tools/mcp/notion_client.rb +21 -19
  26. data/lib/shared_tools/mcp/playwright_client.rb +19 -15
  27. data/lib/shared_tools/mcp/sequential_thinking_client.rb +17 -14
  28. data/lib/shared_tools/mcp/slack_client.rb +30 -25
  29. data/lib/shared_tools/mcp/tavily_client.rb +21 -21
  30. data/lib/shared_tools/mcp.rb +32 -41
  31. data/lib/shared_tools/multi_edit_tool.rb +4 -0
  32. data/lib/shared_tools/parse_ruby_tool.rb +4 -0
  33. data/lib/shared_tools/process_kill_tool.rb +4 -0
  34. data/lib/shared_tools/process_list_tool.rb +4 -0
  35. data/lib/shared_tools/process_output_tool.rb +4 -0
  36. data/lib/shared_tools/process_registry.rb +224 -0
  37. data/lib/shared_tools/process_runner.rb +69 -0
  38. data/lib/shared_tools/process_start_tool.rb +4 -0
  39. data/lib/shared_tools/python_tests_tool.rb +4 -0
  40. data/lib/shared_tools/replace_in_files_tool.rb +4 -0
  41. data/lib/shared_tools/ruby_outline.rb +207 -0
  42. data/lib/shared_tools/run_tests_tool.rb +4 -0
  43. data/lib/shared_tools/text_diff.rb +80 -0
  44. data/lib/shared_tools/todo_write_tool.rb +4 -0
  45. data/lib/shared_tools/toml_parser.rb +409 -0
  46. data/lib/shared_tools/toml_query_tool.rb +4 -0
  47. data/lib/shared_tools/tools/apply_patch_tool.rb +122 -0
  48. data/lib/shared_tools/tools/bash_tool.rb +102 -0
  49. data/lib/shared_tools/tools/browser/click_tool.rb +2 -2
  50. data/lib/shared_tools/tools/browser/inspect_tool.rb +2 -2
  51. data/lib/shared_tools/tools/browser/page_inspect_tool.rb +2 -2
  52. data/lib/shared_tools/tools/browser/page_screenshot_tool.rb +2 -2
  53. data/lib/shared_tools/tools/browser/selector_inspect_tool.rb +2 -2
  54. data/lib/shared_tools/tools/browser/text_field_area_set_tool.rb +2 -2
  55. data/lib/shared_tools/tools/browser/visit_tool.rb +2 -2
  56. data/lib/shared_tools/tools/browser_tool.rb +2 -2
  57. data/lib/shared_tools/tools/bundle_tool.rb +96 -0
  58. data/lib/shared_tools/tools/calculator_tool.rb +2 -2
  59. data/lib/shared_tools/tools/clipboard_tool.rb +2 -2
  60. data/lib/shared_tools/tools/composite_analysis_tool.rb +2 -2
  61. data/lib/shared_tools/tools/computer_tool.rb +2 -2
  62. data/lib/shared_tools/tools/cron_tool.rb +2 -2
  63. data/lib/shared_tools/tools/csv_read_tool.rb +82 -0
  64. data/lib/shared_tools/tools/csv_write_tool.rb +67 -0
  65. data/lib/shared_tools/tools/current_date_time_tool.rb +3 -3
  66. data/lib/shared_tools/tools/data_science_kit.rb +2 -2
  67. data/lib/shared_tools/tools/database_query_tool.rb +2 -2
  68. data/lib/shared_tools/tools/database_tool.rb +2 -2
  69. data/lib/shared_tools/tools/diff_tool.rb +48 -0
  70. data/lib/shared_tools/tools/disk/directory_create_tool.rb +2 -2
  71. data/lib/shared_tools/tools/disk/directory_delete_tool.rb +2 -2
  72. data/lib/shared_tools/tools/disk/directory_list_tool.rb +2 -2
  73. data/lib/shared_tools/tools/disk/directory_move_tool.rb +2 -2
  74. data/lib/shared_tools/tools/disk/file_create_tool.rb +2 -2
  75. data/lib/shared_tools/tools/disk/file_delete_tool.rb +2 -2
  76. data/lib/shared_tools/tools/disk/file_move_tool.rb +2 -2
  77. data/lib/shared_tools/tools/disk/file_read_tool.rb +2 -2
  78. data/lib/shared_tools/tools/disk/file_replace_tool.rb +2 -2
  79. data/lib/shared_tools/tools/disk/file_write_tool.rb +2 -2
  80. data/lib/shared_tools/tools/disk_tool.rb +2 -2
  81. data/lib/shared_tools/tools/dns_tool.rb +2 -2
  82. data/lib/shared_tools/tools/doc/docx_reader_tool.rb +2 -2
  83. data/lib/shared_tools/tools/doc/pdf_reader_tool.rb +2 -2
  84. data/lib/shared_tools/tools/doc/spreadsheet_reader_tool.rb +2 -2
  85. data/lib/shared_tools/tools/doc/text_reader_tool.rb +2 -2
  86. data/lib/shared_tools/tools/doc_tool.rb +2 -2
  87. data/lib/shared_tools/tools/docker/compose_run_tool.rb +2 -2
  88. data/lib/shared_tools/tools/download_file_tool.rb +87 -0
  89. data/lib/shared_tools/tools/enabler.rb +2 -2
  90. data/lib/shared_tools/tools/error_handling_tool.rb +2 -2
  91. data/lib/shared_tools/tools/eval/python_eval_tool.rb +2 -2
  92. data/lib/shared_tools/tools/eval/ruby_eval_tool.rb +2 -2
  93. data/lib/shared_tools/tools/eval/shell_eval_tool.rb +2 -2
  94. data/lib/shared_tools/tools/eval_tool.rb +2 -2
  95. data/lib/shared_tools/tools/gem_tool.rb +157 -0
  96. data/lib/shared_tools/tools/git/add_tool.rb +73 -0
  97. data/lib/shared_tools/tools/git/blame_tool.rb +77 -0
  98. data/lib/shared_tools/tools/git/branch_tool.rb +55 -0
  99. data/lib/shared_tools/tools/git/checkout_tool.rb +69 -0
  100. data/lib/shared_tools/tools/git/commit_tool.rb +73 -0
  101. data/lib/shared_tools/tools/git/diff_tool.rb +69 -0
  102. data/lib/shared_tools/tools/git/grep_tool.rb +82 -0
  103. data/lib/shared_tools/tools/git/helpers.rb +70 -0
  104. data/lib/shared_tools/tools/git/log_tool.rb +71 -0
  105. data/lib/shared_tools/tools/git/show_tool.rb +66 -0
  106. data/lib/shared_tools/tools/git/status_tool.rb +46 -0
  107. data/lib/shared_tools/tools/git.rb +18 -0
  108. data/lib/shared_tools/tools/git_tool.rb +148 -0
  109. data/lib/shared_tools/tools/glob_tool.rb +75 -0
  110. data/lib/shared_tools/tools/http_helpers.rb +119 -0
  111. data/lib/shared_tools/tools/http_request_tool.rb +91 -0
  112. data/lib/shared_tools/tools/json_query_tool.rb +81 -0
  113. data/lib/shared_tools/tools/lint_tool.rb +93 -0
  114. data/lib/shared_tools/tools/multi_edit_tool.rb +134 -0
  115. data/lib/shared_tools/tools/notification_tool.rb +2 -2
  116. data/lib/shared_tools/tools/parse_ruby_tool.rb +127 -0
  117. data/lib/shared_tools/tools/process_kill_tool.rb +55 -0
  118. data/lib/shared_tools/tools/process_list_tool.rb +41 -0
  119. data/lib/shared_tools/tools/process_output_tool.rb +69 -0
  120. data/lib/shared_tools/tools/process_start_tool.rb +0 -0
  121. data/lib/shared_tools/tools/python_tests_tool.rb +105 -0
  122. data/lib/shared_tools/tools/replace_in_files_tool.rb +166 -0
  123. data/lib/shared_tools/tools/run_tests_tool.rb +108 -0
  124. data/lib/shared_tools/tools/search_codebase_tool.rb +182 -51
  125. data/lib/shared_tools/tools/secure_tool_template.rb +2 -2
  126. data/lib/shared_tools/tools/system_info_tool.rb +2 -2
  127. data/lib/shared_tools/tools/todo_write_tool.rb +81 -0
  128. data/lib/shared_tools/tools/toml_query_tool.rb +81 -0
  129. data/lib/shared_tools/tools/toolchain_helpers.rb +64 -0
  130. data/lib/shared_tools/tools/tree_tool.rb +100 -0
  131. data/lib/shared_tools/tools/weather_tool.rb +2 -2
  132. data/lib/shared_tools/tools/web_fetch_tool.rb +85 -0
  133. data/lib/shared_tools/tools/workflow_manager_tool.rb +2 -2
  134. data/lib/shared_tools/tools/yaml_query_tool.rb +86 -0
  135. data/lib/shared_tools/tree_tool.rb +4 -0
  136. data/lib/shared_tools/url_guard.rb +106 -0
  137. data/lib/shared_tools/utilities.rb +2 -2
  138. data/lib/shared_tools/version.rb +1 -1
  139. data/lib/shared_tools/web_fetch_tool.rb +4 -0
  140. data/lib/shared_tools/yaml_query_tool.rb +4 -0
  141. data/lib/shared_tools.rb +1 -1
  142. metadata +532 -409
  143. data/lib/shared_tools/mcp/streamable_http_patch.rb +0 -42
  144. data/lib/shared_tools/tools/version.rb +0 -7
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../shared_tools'
4
+
5
+ module SharedTools
6
+ module Tools
7
+ # Compares two blocks of text and returns a readable line diff. In-process,
8
+ # read-only — no filesystem or git access. For diffing files already on
9
+ # disk, see Git::DiffTool.
10
+ #
11
+ # @example
12
+ # tool = SharedTools::Tools::DiffTool.new
13
+ # tool.execute(old: "a\nb\n", new: "a\nc\n")
14
+ class DiffTool < ::RubyLLM::Tool
15
+ def self.tool_name = 'diff'
16
+
17
+ description "Compare two blocks of text and return a readable line-by-line diff " \
18
+ "(removed lines prefixed -, added lines prefixed +). In-process."
19
+
20
+ parameters do
21
+ string :old, description: "The original text."
22
+ string :new, description: "The changed text."
23
+ string :old_label, description: "Label for the original side. Default 'old'.", required: false
24
+ string :new_label, description: "Label for the changed side. Default 'new'.", required: false
25
+ end
26
+
27
+ # @param logger [Logger] optional logger
28
+ def initialize(logger: nil)
29
+ @logger = logger || RubyLLM.logger
30
+ end
31
+
32
+ # @param old [String]
33
+ # @param new [String]
34
+ # @param old_label [String]
35
+ # @param new_label [String]
36
+ #
37
+ # @return [String]
38
+ def execute(old:, new:, old_label: "old", new_label: "new")
39
+ @logger.info("#{self.class.name}#execute old_label=#{old_label} new_label=#{new_label}")
40
+
41
+ SharedTools::TextDiff.unified(old.to_s, new.to_s, old_label: old_label.to_s, new_label: new_label.to_s)
42
+ rescue => e
43
+ @logger.error("#{self.class.name} failed: #{e.message}")
44
+ { error: e.message }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -9,11 +9,11 @@ module SharedTools
9
9
  # tool = SharedTools::Tools::Disk::DirectoryCreateTool.new(root: "./project")
10
10
  # tool.execute(path: "./foo/bar")
11
11
  class DirectoryCreateTool < ::RubyLLM::Tool
12
- def self.name = 'disk_directory_create'
12
+ def self.tool_name = 'disk_directory_create'
13
13
 
14
14
  description "Creates a directory."
15
15
 
16
- params do
16
+ parameters do
17
17
  string :path, description: "a path to the directory (e.g. `./foo/bar`)"
18
18
  end
19
19
 
@@ -9,11 +9,11 @@ module SharedTools
9
9
  # tool = SharedTools::Tools::Disk::DirectoryDeleteTool.new(root: "./project")
10
10
  # tool.execute(path: "./foo/bar")
11
11
  class DirectoryDeleteTool < ::RubyLLM::Tool
12
- def self.name = 'disk_directory_delete'
12
+ def self.tool_name = 'disk_directory_delete'
13
13
 
14
14
  description "Deletes a directory."
15
15
 
16
- params do
16
+ parameters do
17
17
  string :path, description: "a path to the directory (e.g. `./foo/bar`)"
18
18
  end
19
19
 
@@ -9,11 +9,11 @@ module SharedTools
9
9
  # tool = SharedTools::Tools::Disk::SummaryTool.new(root: "./project")
10
10
  # tool.execute
11
11
  class DirectoryListTool < ::RubyLLM::Tool
12
- def self.name = 'disk_directory_list'
12
+ def self.tool_name = 'disk_directory_list'
13
13
 
14
14
  description "Summarizes the contents (files and directories) of a directory."
15
15
 
16
- params do
16
+ parameters do
17
17
  string :path, description: "a path to the directory (e.g. `./foo/bar`)", required: false
18
18
  end
19
19
 
@@ -9,11 +9,11 @@ module SharedTools
9
9
  # tool = SharedTools::Tools::Disk::DirectoryMoveTool.new(root: "./project")
10
10
  # tool.execute(path: "./foo", destination: "./bar")
11
11
  class DirectoryMoveTool < ::RubyLLM::Tool
12
- def self.name = 'disk_directory_move'
12
+ def self.tool_name = 'disk_directory_move'
13
13
 
14
14
  description "Moves a directory from one location to another."
15
15
 
16
- params do
16
+ parameters do
17
17
  string :path, description: "a path (e.g. `./old`)"
18
18
  string :destination, description: "a path (e.g. `./new`)"
19
19
  end
@@ -9,11 +9,11 @@ module SharedTools
9
9
  # tool = SharedTools::Tools::Disk::FileCreateTool.new(root: "./project")
10
10
  # tool.execute(path: "./README.md")
11
11
  class FileCreateTool < ::RubyLLM::Tool
12
- def self.name = 'disk_file_create'
12
+ def self.tool_name = 'disk_file_create'
13
13
 
14
14
  description "Creates a file."
15
15
 
16
- params do
16
+ parameters do
17
17
  string :path, description: "a path to the file (e.g. `./README.md`)"
18
18
  end
19
19
 
@@ -9,11 +9,11 @@ module SharedTools
9
9
  # tool = SharedTools::Tools::Disk::FileDeleteTool.new(root: "./project")
10
10
  # tool.execute(path: "./README.md")
11
11
  class FileDeleteTool < ::RubyLLM::Tool
12
- def self.name = 'disk_file_delete'
12
+ def self.tool_name = 'disk_file_delete'
13
13
 
14
14
  description "Deletes a file."
15
15
 
16
- params do
16
+ parameters do
17
17
  string :path, description: "a path to the file (e.g. `./README.md`)"
18
18
  end
19
19
 
@@ -12,11 +12,11 @@ module SharedTools
12
12
  # destination: "./README.md",
13
13
  # )
14
14
  class FileMoveTool < ::RubyLLM::Tool
15
- def self.name = 'disk_file_move'
15
+ def self.tool_name = 'disk_file_move'
16
16
 
17
17
  description "Moves a file."
18
18
 
19
- params do
19
+ parameters do
20
20
  string :path, description: "a path (e.g. `./old.rb`)"
21
21
  string :destination, description: "a path (e.g. `./new.rb`)"
22
22
  end
@@ -9,11 +9,11 @@ module SharedTools
9
9
  # tool = SharedTools::Tools::Disk::FileReadTool.new
10
10
  # tool.execute(path: "./README.md") # => "..."
11
11
  class FileReadTool < ::RubyLLM::Tool
12
- def self.name = 'disk_file_read'
12
+ def self.tool_name = 'disk_file_read'
13
13
 
14
14
  description "Reads the contents of a file."
15
15
 
16
- params do
16
+ parameters do
17
17
  string :path, description: "a path (e.g. `./main.rb`)"
18
18
  end
19
19
 
@@ -13,11 +13,11 @@ module SharedTools
13
13
  # path: "README.md"
14
14
  # )
15
15
  class FileReplaceTool < ::RubyLLM::Tool
16
- def self.name = 'disk_file_replace'
16
+ def self.tool_name = 'disk_file_replace'
17
17
 
18
18
  description "Replaces a specific string in a file (old_text => new_text)."
19
19
 
20
- params do
20
+ parameters do
21
21
  string :old_text, description: "the old text (e.g. `puts 'ABC'`)"
22
22
  string :new_text, description: "the new text (e.g. `puts 'DEF'`)"
23
23
  string :path, description: "a path (e.g. `./main.rb`)"
@@ -9,11 +9,11 @@ module SharedTools
9
9
  # tool = SharedTools::Tools::Disk::FileWriteTool.new(root: "./project")
10
10
  # tool.execute(path: "./README.md", text: "Hello World")
11
11
  class FileWriteTool < ::RubyLLM::Tool
12
- def self.name = 'disk_file_write'
12
+ def self.tool_name = 'disk_file_write'
13
13
 
14
14
  description "Writes the contents of a file."
15
15
 
16
- params do
16
+ parameters do
17
17
  string :path, description: "a path for the file (e.g. `./main.rb`)"
18
18
  string :text, description: "the text to write to the file (e.g. `puts 'Hello World'`)"
19
19
  end
@@ -13,7 +13,7 @@ module SharedTools
13
13
  # disk.execute(action: SharedTools::Tools::DiskTool::Action::FILE_READ, path: "./demo.rb")
14
14
  # disk.execute(action: SharedTools::Tools::DiskTool::Action::FILE_DELETE, path: "./demo.rb")
15
15
  class DiskTool < ::RubyLLM::Tool
16
- def self.name = 'disk_tool'
16
+ def self.tool_name = 'disk'
17
17
  description <<~TEXT
18
18
  A tool for interacting with a system. It is able to list, create, delete, move and modify directories and files.
19
19
  TEXT
@@ -44,7 +44,7 @@ module SharedTools
44
44
  Action::FILE_REPLACE,
45
45
  ].freeze
46
46
 
47
- params do
47
+ parameters do
48
48
  string :action, description: <<~TEXT.strip
49
49
  Options:
50
50
  * `#{Action::DIRECTORY_CREATE}`: creates a directory at a specific `path`
@@ -20,7 +20,7 @@ module SharedTools
20
20
  # tool.execute(action: 'whois', host: 'github.com')
21
21
  # tool.execute(action: 'whois', host: '8.8.8.8')
22
22
  class DnsTool < ::RubyLLM::Tool
23
- def self.name = 'dns_tool'
23
+ def self.tool_name = 'dns'
24
24
 
25
25
  description <<~DESC
26
26
  Perform DNS lookups, reverse lookups, record queries, external IP detection,
@@ -40,7 +40,7 @@ module SharedTools
40
40
  - 'whois' — Query the WHOIS database for a domain name or IP address
41
41
  DESC
42
42
 
43
- params do
43
+ parameters do
44
44
  string :action, description: <<~DESC.strip
45
45
  The DNS or network operation to perform. One of:
46
46
  - 'a' — Look up IPv4 A records for a hostname. Returns the IPv4 addresses
@@ -16,11 +16,11 @@ module SharedTools
16
16
  # tool.execute(doc_path: "./report.docx")
17
17
  # tool.execute(doc_path: "./report.docx", paragraph_range: "1-10")
18
18
  class DocxReaderTool < ::RubyLLM::Tool
19
- def self.name = 'doc_docx_read'
19
+ def self.tool_name = 'doc_docx_read'
20
20
 
21
21
  description "Read the text content of a Microsoft Word (.docx) document."
22
22
 
23
- params do
23
+ parameters do
24
24
  string :doc_path, description: "Path to the .docx file."
25
25
 
26
26
  string :paragraph_range, description: <<~DESC.strip, required: false
@@ -14,11 +14,11 @@ module SharedTools
14
14
  # tool = SharedTools::Tools::Doc::PdfReaderTool.new
15
15
  # tool.execute(doc_path: "./document.pdf", page_numbers: "1, 5, 10")
16
16
  class PdfReaderTool < ::RubyLLM::Tool
17
- def self.name = 'doc_pdf_read'
17
+ def self.tool_name = 'doc_pdf_read'
18
18
 
19
19
  description "Read the text of any set of pages from a PDF document."
20
20
 
21
- params do
21
+ parameters do
22
22
  string :page_numbers, description: 'Comma-separated page numbers (first page: 1). (e.g. "12, 14, 15")'
23
23
  string :doc_path, description: "Path to the PDF document."
24
24
  end
@@ -21,13 +21,13 @@ module SharedTools
21
21
  # @example Read a row range from a worksheet
22
22
  # tool.execute(doc_path: "./report.xlsx", sheet: "Summary", row_range: "2-50")
23
23
  class SpreadsheetReaderTool < ::RubyLLM::Tool
24
- def self.name = 'doc_spreadsheet_read'
24
+ def self.tool_name = 'doc_spreadsheet_read'
25
25
 
26
26
  description "Read tabular data from spreadsheet files (CSV, XLSX, ODS, and other formats)."
27
27
 
28
28
  SUPPORTED_FORMATS = %w[.csv .xlsx .ods .xlsm].freeze
29
29
 
30
- params do
30
+ parameters do
31
31
  string :doc_path, description: <<~DESC.strip
32
32
  Path to the spreadsheet file. Supported formats:
33
33
  - .csv — Comma-separated values (plain text, no gem beyond roo required)
@@ -9,11 +9,11 @@ module SharedTools
9
9
  # tool = SharedTools::Tools::Doc::TextReaderTool.new
10
10
  # tool.execute(doc_path: "./guide.txt")
11
11
  class TextReaderTool < ::RubyLLM::Tool
12
- def self.name = 'doc_text_read'
12
+ def self.tool_name = 'doc_text_read'
13
13
 
14
14
  description "Read the full contents of a plain text file."
15
15
 
16
- params do
16
+ parameters do
17
17
  string :doc_path, description: "Path to the text file to read."
18
18
  end
19
19
 
@@ -6,7 +6,7 @@ module SharedTools
6
6
  module Tools
7
7
  # A tool for reading and processing documents
8
8
  class DocTool < ::RubyLLM::Tool
9
- def self.name = 'doc_tool'
9
+ def self.tool_name = 'doc'
10
10
 
11
11
  module Action
12
12
  PDF_READ = "pdf_read"
@@ -85,7 +85,7 @@ module SharedTools
85
85
  {"action": "#{Action::SPREADSHEET_READ}", "doc_path": "./report.xlsx", "row_range": "2-50", "headers": false}
86
86
  TEXT
87
87
 
88
- params do
88
+ parameters do
89
89
  string :action, description: <<~TEXT.strip
90
90
  The document action to perform. Options:
91
91
  * `#{Action::TEXT_READ}`: Read a plain text file
@@ -9,11 +9,11 @@ module SharedTools
9
9
  # tool = SharedTools::Tools::Docker::ComposeRunTool.new
10
10
  # tool.execute(service: "app", command: "rspec", args: ["spec/main_spec.rb"])
11
11
  class ComposeRunTool < ::RubyLLM::Tool
12
- def self.name = 'docker_compose_run'
12
+ def self.tool_name = 'docker_compose_run'
13
13
 
14
14
  description "Runs a command via Docker with arguments on the project (e.g. `rspec spec/main_spec.rb`)."
15
15
 
16
- params do
16
+ parameters do
17
17
  string :service, description: "The service to run the command on (e.g. `app`).", required: false
18
18
  string :command, description: "The command to run (e.g. `rspec`)."
19
19
  array :args, of: :string, description: "The arguments for the command.", required: false
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "pathname"
5
+ require_relative "../../shared_tools"
6
+ require_relative "http_helpers"
7
+
8
+ module SharedTools
9
+ module Tools
10
+ # Downloads a URL to a file within root. The request goes through
11
+ # UrlGuard (so it can't be pointed at internal/loopback/metadata
12
+ # addresses), follows redirects safely, and is capped at
13
+ # HttpHelpers::MAX_FETCH_BYTES. The destination path is confined to
14
+ # root. Mutating — requires user authorization (see
15
+ # SharedTools.execute?).
16
+ #
17
+ # @example
18
+ # tool = SharedTools::Tools::DownloadFileTool.new(root: "./downloads")
19
+ # tool.execute(url: "https://example.com/file.zip", path: "file.zip")
20
+ class DownloadFileTool < ::RubyLLM::Tool
21
+ include HttpHelpers
22
+
23
+ def self.tool_name = 'download_file'
24
+
25
+ description "Download an http/https URL to a file within root. SSRF-protected and " \
26
+ "size-capped. Use for fetching an asset or release to disk (web_fetch returns text " \
27
+ "instead). Overwrites an existing file."
28
+
29
+ parameters do
30
+ string :url, description: "The http/https URL to download."
31
+ string :path, description: "Destination path, relative to root."
32
+ end
33
+
34
+ # @param root [String] optional, defaults to the current directory
35
+ # @param logger [Logger] optional logger
36
+ def initialize(root: nil, logger: nil)
37
+ @root = root || Dir.pwd
38
+ @logger = logger || RubyLLM.logger
39
+ end
40
+
41
+ # @param url [String]
42
+ # @param path [String]
43
+ #
44
+ # @return [String, Hash]
45
+ def execute(url:, path:)
46
+ @logger.info("#{self.class.name}#execute url=#{url.inspect} path=#{path.inspect}")
47
+
48
+ real = resolve!(path)
49
+ return { error: "path is a directory: #{path}" } if File.directory?(real)
50
+
51
+ allowed = SharedTools.execute?(tool: self.class.to_s, stuff: "Download #{url} to #{path}")
52
+ unless allowed
53
+ @logger.warn("User declined to download #{url}")
54
+ return { error: "User declined to download the file" }
55
+ end
56
+
57
+ response = guarded_get(url)
58
+ return { error: "server returned HTTP #{response.status} for #{response.final_url}" } if response.status >= 400
59
+
60
+ body = response.body.to_s
61
+ FileUtils.mkdir_p(File.dirname(real))
62
+ File.binwrite(real, body)
63
+
64
+ "Downloaded #{body.bytesize} bytes from #{response.final_url} to #{path}"
65
+ rescue SecurityError => e
66
+ @logger.error("#{self.class.name} path denied: #{e.message}")
67
+ { error: e.message }
68
+ rescue SharedTools::UrlGuard::Blocked => e
69
+ @logger.error("#{self.class.name} url blocked: #{e.message}")
70
+ { error: e.message }
71
+ rescue HttpHelpers::FetchError => e
72
+ @logger.error("#{self.class.name} download failed: #{e.message}")
73
+ { error: "download failed: #{e.message}" }
74
+ end
75
+
76
+ private
77
+
78
+ def resolve!(path)
79
+ root = Pathname.new(File.expand_path(@root))
80
+ resolved = (root + path).cleanpath
81
+ raise SecurityError, "path escapes root: #{path}" unless resolved.ascend.any? { |ancestor| ancestor == root }
82
+
83
+ resolved.to_s
84
+ end
85
+ end
86
+ end
87
+ end
@@ -24,8 +24,8 @@ module Tools
24
24
  description <<~DESCRIPTION
25
25
  Enables a tool based on the request.
26
26
  DESCRIPTION
27
- param :tool, desc: 'Tool name to enable'
28
- # param :params, desc: 'JSON formatted parameters'
27
+ parameter :tool, description: 'Tool name to enable'
28
+ # parameter :params, description: 'JSON formatted parameters', required: false
29
29
 
30
30
  def initialize(agent)
31
31
  @agent = agent
@@ -22,7 +22,7 @@ module SharedTools
22
22
  class ResourceNotFoundError < StandardError; end
23
23
 
24
24
  class ErrorHandlingTool < RubyLLM::Tool
25
- def self.name = 'error_handling_tool'
25
+ def self.tool_name = 'error_handling'
26
26
 
27
27
  description <<~'DESCRIPTION'
28
28
  Reference tool demonstrating comprehensive error handling patterns and resilience strategies
@@ -52,7 +52,7 @@ module SharedTools
52
52
  )
53
53
  DESCRIPTION
54
54
 
55
- params do
55
+ parameters do
56
56
  string :operation, description: <<~DESC.strip
57
57
  The operation to perform. Options:
58
58
  - 'validate': Validate data structure and content
@@ -8,7 +8,7 @@ module SharedTools
8
8
  # tool = SharedTools::Tools::Eval::PythonEvalTool.new
9
9
  # tool.execute(code: "print('Hello'); result = 2 + 2")
10
10
  class PythonEvalTool < ::RubyLLM::Tool
11
- def self.name = 'eval_python'
11
+ def self.tool_name = 'eval_python'
12
12
 
13
13
  description <<~DESCRIPTION
14
14
  Execute Python source code safely and return the result.
@@ -21,7 +21,7 @@ module SharedTools
21
21
  NOTE: Requires python3 to be available in the system PATH.
22
22
  DESCRIPTION
23
23
 
24
- params do
24
+ parameters do
25
25
  string :code, description: "The Python code to execute"
26
26
  end
27
27
 
@@ -9,7 +9,7 @@ module SharedTools
9
9
  # tool = SharedTools::Tools::Eval::RubyEvalTool.new
10
10
  # tool.execute(code: "puts 'Hello'; 2 + 2")
11
11
  class RubyEvalTool < ::RubyLLM::Tool
12
- def self.name = 'eval_ruby'
12
+ def self.tool_name = 'eval_ruby'
13
13
 
14
14
  description <<~DESCRIPTION
15
15
  Execute Ruby source code safely and return the result.
@@ -20,7 +20,7 @@ module SharedTools
20
20
  WARNING: This tool executes arbitrary Ruby code. Use with caution.
21
21
  DESCRIPTION
22
22
 
23
- params do
23
+ parameters do
24
24
  string :code, description: "The Ruby code to execute"
25
25
  end
26
26
 
@@ -13,11 +13,11 @@ module SharedTools
13
13
  STDOUT_MAX = 5000
14
14
  STDERR_MAX = 2000
15
15
 
16
- def self.name = 'eval_shell'
16
+ def self.tool_name = 'eval_shell'
17
17
 
18
18
  description "Execute a shell command safely and return the result."
19
19
 
20
- params do
20
+ parameters do
21
21
  string :command, description: "The shell command to execute"
22
22
  string :workdir, description: "Working directory for the command (default: current directory)", required: false
23
23
  end
@@ -6,7 +6,7 @@ module SharedTools
6
6
  module Tools
7
7
  # A tool for evaluating code in different programming languages
8
8
  class EvalTool < ::RubyLLM::Tool
9
- def self.name = 'eval_tool'
9
+ def self.tool_name = 'eval'
10
10
 
11
11
  module Action
12
12
  RUBY = "ruby"
@@ -58,7 +58,7 @@ module SharedTools
58
58
  {"action": "#{Action::SHELL}", "command": "uname -a"}
59
59
  TEXT
60
60
 
61
- params do
61
+ parameters do
62
62
  string :action, description: <<~TEXT.strip
63
63
  The evaluation action to perform. Options:
64
64
  * `#{Action::RUBY}`: Execute Ruby code