shared_tools 0.2.3 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/README.md +594 -42
- data/lib/shared_tools/{ruby_llm/mcp → mcp}/github_mcp_server.rb +20 -3
- data/lib/shared_tools/mcp/imcp.rb +28 -0
- data/lib/shared_tools/mcp/tavily_mcp_server.rb +44 -0
- data/lib/shared_tools/mcp.rb +24 -0
- data/lib/shared_tools/tools/browser/base_driver.rb +64 -0
- data/lib/shared_tools/tools/browser/base_tool.rb +50 -0
- data/lib/shared_tools/tools/browser/click_tool.rb +54 -0
- data/lib/shared_tools/tools/browser/elements/element_grouper.rb +73 -0
- data/lib/shared_tools/tools/browser/elements/nearby_element_detector.rb +109 -0
- data/lib/shared_tools/tools/browser/formatters/action_formatter.rb +37 -0
- data/lib/shared_tools/tools/browser/formatters/data_entry_formatter.rb +135 -0
- data/lib/shared_tools/tools/browser/formatters/element_formatter.rb +52 -0
- data/lib/shared_tools/tools/browser/formatters/input_formatter.rb +59 -0
- data/lib/shared_tools/tools/browser/inspect_tool.rb +87 -0
- data/lib/shared_tools/tools/browser/inspect_utils.rb +51 -0
- data/lib/shared_tools/tools/browser/page_inspect/button_summarizer.rb +140 -0
- data/lib/shared_tools/tools/browser/page_inspect/form_summarizer.rb +98 -0
- data/lib/shared_tools/tools/browser/page_inspect/html_summarizer.rb +37 -0
- data/lib/shared_tools/tools/browser/page_inspect/link_summarizer.rb +103 -0
- data/lib/shared_tools/tools/browser/page_inspect_tool.rb +55 -0
- data/lib/shared_tools/tools/browser/page_screenshot_tool.rb +39 -0
- data/lib/shared_tools/tools/browser/selector_generator/base_selectors.rb +28 -0
- data/lib/shared_tools/tools/browser/selector_generator/contextual_selectors.rb +140 -0
- data/lib/shared_tools/tools/browser/selector_generator.rb +73 -0
- data/lib/shared_tools/tools/browser/selector_inspect_tool.rb +67 -0
- data/lib/shared_tools/tools/browser/text_field_area_set_tool.rb +45 -0
- data/lib/shared_tools/tools/browser/visit_tool.rb +43 -0
- data/lib/shared_tools/tools/browser/watir_driver.rb +132 -0
- data/lib/shared_tools/tools/browser.rb +27 -0
- data/lib/shared_tools/tools/browser_tool.rb +255 -0
- data/lib/shared_tools/tools/calculator_tool.rb +169 -0
- data/lib/shared_tools/tools/composite_analysis_tool.rb +520 -0
- data/lib/shared_tools/tools/computer/base_driver.rb +177 -0
- data/lib/shared_tools/tools/computer/mac_driver.rb +103 -0
- data/lib/shared_tools/tools/computer.rb +21 -0
- data/lib/shared_tools/tools/computer_tool.rb +207 -0
- data/lib/shared_tools/tools/data_science_kit.rb +707 -0
- data/lib/shared_tools/tools/database/base_driver.rb +17 -0
- data/lib/shared_tools/tools/database/postgres_driver.rb +30 -0
- data/lib/shared_tools/tools/database/sqlite_driver.rb +29 -0
- data/lib/shared_tools/tools/database.rb +9 -0
- data/lib/shared_tools/tools/database_query_tool.rb +313 -0
- data/lib/shared_tools/tools/database_tool.rb +99 -0
- data/lib/shared_tools/tools/devops_toolkit.rb +420 -0
- data/lib/shared_tools/tools/disk/base_driver.rb +91 -0
- data/lib/shared_tools/tools/disk/base_tool.rb +20 -0
- data/lib/shared_tools/tools/disk/directory_create_tool.rb +39 -0
- data/lib/shared_tools/tools/disk/directory_delete_tool.rb +39 -0
- data/lib/shared_tools/tools/disk/directory_list_tool.rb +37 -0
- data/lib/shared_tools/tools/disk/directory_move_tool.rb +40 -0
- data/lib/shared_tools/tools/disk/file_create_tool.rb +38 -0
- data/lib/shared_tools/tools/disk/file_delete_tool.rb +40 -0
- data/lib/shared_tools/tools/disk/file_move_tool.rb +43 -0
- data/lib/shared_tools/tools/disk/file_read_tool.rb +40 -0
- data/lib/shared_tools/tools/disk/file_replace_tool.rb +44 -0
- data/lib/shared_tools/tools/disk/file_write_tool.rb +40 -0
- data/lib/shared_tools/tools/disk/local_driver.rb +91 -0
- data/lib/shared_tools/tools/disk.rb +17 -0
- data/lib/shared_tools/tools/disk_tool.rb +132 -0
- data/lib/shared_tools/tools/doc/pdf_reader_tool.rb +79 -0
- data/lib/shared_tools/tools/doc.rb +8 -0
- data/lib/shared_tools/tools/doc_tool.rb +109 -0
- data/lib/shared_tools/tools/docker/base_tool.rb +56 -0
- data/lib/shared_tools/tools/docker/compose_run_tool.rb +77 -0
- data/lib/shared_tools/tools/docker.rb +8 -0
- data/lib/shared_tools/tools/error_handling_tool.rb +403 -0
- data/lib/shared_tools/tools/eval/python_eval_tool.rb +209 -0
- data/lib/shared_tools/tools/eval/ruby_eval_tool.rb +93 -0
- data/lib/shared_tools/tools/eval/shell_eval_tool.rb +64 -0
- data/lib/shared_tools/tools/eval.rb +10 -0
- data/lib/shared_tools/tools/eval_tool.rb +139 -0
- data/lib/shared_tools/tools/secure_tool_template.rb +353 -0
- data/lib/shared_tools/tools/version.rb +7 -0
- data/lib/shared_tools/tools/weather_tool.rb +197 -0
- data/lib/shared_tools/tools/workflow_manager_tool.rb +312 -0
- data/lib/shared_tools/tools.rb +16 -0
- data/lib/shared_tools/version.rb +1 -1
- data/lib/shared_tools.rb +9 -24
- metadata +189 -68
- data/lib/shared_tools/llm_rb/run_shell_command.rb +0 -23
- data/lib/shared_tools/llm_rb.rb +0 -9
- data/lib/shared_tools/omniai.rb +0 -9
- data/lib/shared_tools/raix/what_is_the_weather.rb +0 -18
- data/lib/shared_tools/raix.rb +0 -9
- data/lib/shared_tools/ruby_llm/edit_file.rb +0 -71
- data/lib/shared_tools/ruby_llm/incomplete/calculator_tool.rb +0 -70
- data/lib/shared_tools/ruby_llm/incomplete/composite_analysis_tool.rb +0 -89
- data/lib/shared_tools/ruby_llm/incomplete/data_science_kit.rb +0 -128
- data/lib/shared_tools/ruby_llm/incomplete/database_query_tool.rb +0 -100
- data/lib/shared_tools/ruby_llm/incomplete/devops_toolkit.rb +0 -112
- data/lib/shared_tools/ruby_llm/incomplete/error_handling_tool.rb +0 -109
- data/lib/shared_tools/ruby_llm/incomplete/secure_tool_template.rb +0 -117
- data/lib/shared_tools/ruby_llm/incomplete/weather_tool.rb +0 -110
- data/lib/shared_tools/ruby_llm/incomplete/workflow_manager_tool.rb +0 -145
- data/lib/shared_tools/ruby_llm/list_files.rb +0 -49
- data/lib/shared_tools/ruby_llm/mcp/imcp.rb +0 -15
- data/lib/shared_tools/ruby_llm/mcp.rb +0 -12
- data/lib/shared_tools/ruby_llm/pdf_page_reader.rb +0 -59
- data/lib/shared_tools/ruby_llm/python_eval.rb +0 -194
- data/lib/shared_tools/ruby_llm/read_file.rb +0 -40
- data/lib/shared_tools/ruby_llm/ruby_eval.rb +0 -77
- data/lib/shared_tools/ruby_llm/run_shell_command.rb +0 -49
- data/lib/shared_tools/ruby_llm.rb +0 -12
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "local_driver"
|
|
4
|
+
|
|
5
|
+
module SharedTools
|
|
6
|
+
module Tools
|
|
7
|
+
module Disk
|
|
8
|
+
# @example
|
|
9
|
+
# tool = SharedTools::Tools::Disk::FileCreateTool.new(root: "./project")
|
|
10
|
+
# tool.execute(path: "./README.md")
|
|
11
|
+
class FileCreateTool < ::RubyLLM::Tool
|
|
12
|
+
def self.name = 'disk_file_create'
|
|
13
|
+
|
|
14
|
+
description "Creates a file."
|
|
15
|
+
|
|
16
|
+
params do
|
|
17
|
+
string :path, description: "a path to the file (e.g. `./README.md`)"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(driver: nil, logger: nil)
|
|
21
|
+
@driver = driver || SharedTools::Tools::Disk::LocalDriver.new(root: Dir.pwd)
|
|
22
|
+
@logger = logger || RubyLLM.logger
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @param path [String]
|
|
26
|
+
#
|
|
27
|
+
# @return [String]
|
|
28
|
+
def execute(path:)
|
|
29
|
+
@logger.info("#{self.class.name}#execute path=#{path.inspect}")
|
|
30
|
+
@driver.file_create(path:)
|
|
31
|
+
rescue SecurityError => e
|
|
32
|
+
@logger.error(e.message)
|
|
33
|
+
raise e
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "local_driver"
|
|
4
|
+
|
|
5
|
+
module SharedTools
|
|
6
|
+
module Tools
|
|
7
|
+
module Disk
|
|
8
|
+
# @example
|
|
9
|
+
# tool = SharedTools::Tools::Disk::FileDeleteTool.new(root: "./project")
|
|
10
|
+
# tool.execute(path: "./README.md")
|
|
11
|
+
class FileDeleteTool < ::RubyLLM::Tool
|
|
12
|
+
def self.name = 'disk_file_delete'
|
|
13
|
+
|
|
14
|
+
description "Deletes a file."
|
|
15
|
+
|
|
16
|
+
params do
|
|
17
|
+
string :path, description: "a path to the file (e.g. `./README.md`)"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(driver: nil, logger: nil)
|
|
21
|
+
@driver = driver || SharedTools::Tools::Disk::LocalDriver.new(root: Dir.pwd)
|
|
22
|
+
@logger = logger || RubyLLM.logger
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @param path [String]
|
|
26
|
+
#
|
|
27
|
+
# @raise [SecurityError]
|
|
28
|
+
#
|
|
29
|
+
# @return [String]
|
|
30
|
+
def execute(path:)
|
|
31
|
+
@logger.info("#{self.class.name}#execute path=#{path.inspect}")
|
|
32
|
+
@driver.file_delete(path:)
|
|
33
|
+
rescue SecurityError => e
|
|
34
|
+
@logger.error(e.message)
|
|
35
|
+
raise e
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "local_driver"
|
|
4
|
+
|
|
5
|
+
module SharedTools
|
|
6
|
+
module Tools
|
|
7
|
+
module Disk
|
|
8
|
+
# @example
|
|
9
|
+
# tool = SharedTools::Tools::Disk::FileMoveTool.new(root: "./project")
|
|
10
|
+
# tool.execute(
|
|
11
|
+
# path: "./README.txt",
|
|
12
|
+
# destination: "./README.md",
|
|
13
|
+
# )
|
|
14
|
+
class FileMoveTool < ::RubyLLM::Tool
|
|
15
|
+
def self.name = 'disk_file_move'
|
|
16
|
+
|
|
17
|
+
description "Moves a file."
|
|
18
|
+
|
|
19
|
+
params do
|
|
20
|
+
string :path, description: "a path (e.g. `./old.rb`)"
|
|
21
|
+
string :destination, description: "a path (e.g. `./new.rb`)"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initialize(driver: nil, logger: nil)
|
|
25
|
+
@driver = driver || SharedTools::Tools::Disk::LocalDriver.new(root: Dir.pwd)
|
|
26
|
+
@logger = logger || RubyLLM.logger
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @param path [String]
|
|
30
|
+
# @param destination [String]
|
|
31
|
+
#
|
|
32
|
+
# @return [String]
|
|
33
|
+
def execute(path:, destination:)
|
|
34
|
+
@logger.info("#{self.class.name}#execute path=#{path.inspect} destination=#{destination.inspect}")
|
|
35
|
+
@driver.file_move(path:, destination:)
|
|
36
|
+
rescue SecurityError => e
|
|
37
|
+
@logger.info("ERROR: #{e.message}")
|
|
38
|
+
raise e
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "local_driver"
|
|
4
|
+
|
|
5
|
+
module SharedTools
|
|
6
|
+
module Tools
|
|
7
|
+
module Disk
|
|
8
|
+
# @example
|
|
9
|
+
# tool = SharedTools::Tools::Disk::FileReadTool.new
|
|
10
|
+
# tool.execute(path: "./README.md") # => "..."
|
|
11
|
+
class FileReadTool < ::RubyLLM::Tool
|
|
12
|
+
def self.name = 'disk_file_read'
|
|
13
|
+
|
|
14
|
+
description "Reads the contents of a file."
|
|
15
|
+
|
|
16
|
+
params do
|
|
17
|
+
string :path, description: "a path (e.g. `./main.rb`)"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @param driver [SharedTools::Tools::Disk::BaseDriver] optional, defaults to LocalDriver with current directory
|
|
21
|
+
# @param logger [Logger] optional logger
|
|
22
|
+
def initialize(driver: nil, logger: nil)
|
|
23
|
+
@driver = driver || SharedTools::Tools::Disk::LocalDriver.new(root: Dir.pwd)
|
|
24
|
+
@logger = logger || RubyLLM.logger
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @param path [String]
|
|
28
|
+
#
|
|
29
|
+
# @return [String]
|
|
30
|
+
def execute(path:)
|
|
31
|
+
@logger.info("#{self.class.name}#execute path=#{path}")
|
|
32
|
+
@driver.file_read(path:)
|
|
33
|
+
rescue StandardError => e
|
|
34
|
+
@logger.error(e.message)
|
|
35
|
+
raise e
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "local_driver"
|
|
4
|
+
|
|
5
|
+
module SharedTools
|
|
6
|
+
module Tools
|
|
7
|
+
module Disk
|
|
8
|
+
# @example
|
|
9
|
+
# tool = SharedTools::Tools::Disk::FileReadTool.new(root: "./project")
|
|
10
|
+
# tool.execute(
|
|
11
|
+
# old_text: 'puts "ABC"',
|
|
12
|
+
# new_text: 'puts "DEF"',
|
|
13
|
+
# path: "README.md"
|
|
14
|
+
# )
|
|
15
|
+
class FileReplaceTool < ::RubyLLM::Tool
|
|
16
|
+
def self.name = 'disk_file_replace'
|
|
17
|
+
|
|
18
|
+
description "Replaces a specific string in a file (old_text => new_text)."
|
|
19
|
+
|
|
20
|
+
params do
|
|
21
|
+
string :old_text, description: "the old text (e.g. `puts 'ABC'`)"
|
|
22
|
+
string :new_text, description: "the new text (e.g. `puts 'DEF'`)"
|
|
23
|
+
string :path, description: "a path (e.g. `./main.rb`)"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def initialize(driver: nil, logger: nil)
|
|
27
|
+
@driver = driver || SharedTools::Tools::Disk::LocalDriver.new(root: Dir.pwd)
|
|
28
|
+
@logger = logger || RubyLLM.logger
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param path [String]
|
|
32
|
+
# @param old_text [String]
|
|
33
|
+
# @param new_text [String]
|
|
34
|
+
def execute(old_text:, new_text:, path:)
|
|
35
|
+
@logger.info %(#{self.class.name}#execute old_text="#{old_text}" new_text="#{new_text}" path="#{path}")
|
|
36
|
+
@driver.file_replace(old_text:, new_text:, path:)
|
|
37
|
+
rescue SecurityError => e
|
|
38
|
+
@logger.error(e.message)
|
|
39
|
+
raise e
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "local_driver"
|
|
4
|
+
|
|
5
|
+
module SharedTools
|
|
6
|
+
module Tools
|
|
7
|
+
module Disk
|
|
8
|
+
# @example
|
|
9
|
+
# tool = SharedTools::Tools::Disk::FileWriteTool.new(root: "./project")
|
|
10
|
+
# tool.execute(path: "./README.md", text: "Hello World")
|
|
11
|
+
class FileWriteTool < ::RubyLLM::Tool
|
|
12
|
+
def self.name = 'disk_file_write'
|
|
13
|
+
|
|
14
|
+
description "Writes the contents of a file."
|
|
15
|
+
|
|
16
|
+
params do
|
|
17
|
+
string :path, description: "a path for the file (e.g. `./main.rb`)"
|
|
18
|
+
string :text, description: "the text to write to the file (e.g. `puts 'Hello World'`)"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(driver: nil, logger: nil)
|
|
22
|
+
@driver = driver || SharedTools::Tools::Disk::LocalDriver.new(root: Dir.pwd)
|
|
23
|
+
@logger = logger || RubyLLM.logger
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param path [String]
|
|
27
|
+
# @param text [String]
|
|
28
|
+
#
|
|
29
|
+
# @return [String]
|
|
30
|
+
def execute(path:, text:)
|
|
31
|
+
@logger.info("#{self.class.name}#execute path=#{path}")
|
|
32
|
+
@driver.file_write(path:, text:)
|
|
33
|
+
rescue SecurityError => e
|
|
34
|
+
@logger.error("ERROR: #{e.message}")
|
|
35
|
+
raise e
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_driver"
|
|
4
|
+
|
|
5
|
+
module SharedTools
|
|
6
|
+
module Tools
|
|
7
|
+
module Disk
|
|
8
|
+
# A driver for interacting with a disk via various operations
|
|
9
|
+
class LocalDriver < BaseDriver
|
|
10
|
+
# @raise [SecurityError]
|
|
11
|
+
#
|
|
12
|
+
# @param path [String]
|
|
13
|
+
def directory_create(path:)
|
|
14
|
+
FileUtils.mkdir_p(resolve!(path:))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @param path [String]
|
|
18
|
+
def directory_delete(path:)
|
|
19
|
+
FileUtils.rmdir(resolve!(path:))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @param path [String] optional
|
|
23
|
+
def directory_list(path: ".")
|
|
24
|
+
Dir.chdir(resolve!(path:)) do
|
|
25
|
+
Dir.glob("**/*").map { |path| summarize(path:) }.join("\n")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @param path [String]
|
|
30
|
+
# @param destination [String]
|
|
31
|
+
def directory_move(path:, destination:)
|
|
32
|
+
FileUtils.mv(resolve!(path:), resolve!(path: destination))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @param path [String]
|
|
36
|
+
def file_create(path:)
|
|
37
|
+
path = resolve!(path:)
|
|
38
|
+
FileUtils.touch(path) unless File.exist?(path)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @param path [String]
|
|
42
|
+
def file_delete(path:)
|
|
43
|
+
File.delete(resolve!(path:))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @param path [String]
|
|
47
|
+
# @param destination [String]
|
|
48
|
+
def file_move(path:, destination:)
|
|
49
|
+
FileUtils.mv(resolve!(path:), resolve!(path: destination))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @param path [String]
|
|
53
|
+
#
|
|
54
|
+
# @return [String]
|
|
55
|
+
def file_read(path:)
|
|
56
|
+
File.read(resolve!(path:))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @param old_text [String]
|
|
60
|
+
# @param new_text [String]
|
|
61
|
+
# @param path [String]
|
|
62
|
+
def file_replace(old_text:, new_text:, path:)
|
|
63
|
+
path = resolve!(path:)
|
|
64
|
+
contents = File.read(path)
|
|
65
|
+
text = contents.gsub(old_text, new_text)
|
|
66
|
+
File.write(path, text)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @param path [String]
|
|
70
|
+
# @param text [String]
|
|
71
|
+
def file_write(path:, text:)
|
|
72
|
+
File.write(resolve!(path:), text)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
protected
|
|
76
|
+
|
|
77
|
+
# @param path [String]
|
|
78
|
+
#
|
|
79
|
+
# @raise [SecurityError]
|
|
80
|
+
#
|
|
81
|
+
# @return Pathname
|
|
82
|
+
def resolve!(path:)
|
|
83
|
+
@root.join(path).tap do |resolved|
|
|
84
|
+
relative = resolved.ascend.any? { |ancestor| ancestor.eql?(@root) }
|
|
85
|
+
raise SecurityError, "unknown path=#{resolved}" unless relative
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Collection loader for all disk tools
|
|
4
|
+
# Usage: require 'shared_tools/tools/disk'
|
|
5
|
+
|
|
6
|
+
require 'shared_tools'
|
|
7
|
+
|
|
8
|
+
require_relative 'disk/file_read_tool'
|
|
9
|
+
require_relative 'disk/file_write_tool'
|
|
10
|
+
require_relative 'disk/file_create_tool'
|
|
11
|
+
require_relative 'disk/file_delete_tool'
|
|
12
|
+
require_relative 'disk/file_move_tool'
|
|
13
|
+
require_relative 'disk/file_replace_tool'
|
|
14
|
+
require_relative 'disk/directory_list_tool'
|
|
15
|
+
require_relative 'disk/directory_create_tool'
|
|
16
|
+
require_relative 'disk/directory_delete_tool'
|
|
17
|
+
require_relative 'disk/directory_move_tool'
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../shared_tools'
|
|
4
|
+
|
|
5
|
+
module SharedTools
|
|
6
|
+
module Tools
|
|
7
|
+
# A tool for interacting with files and directories. Be careful using as it can perform actions on your computer!
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# disk = SharedTools::Tools::DiskTool.new
|
|
11
|
+
# disk.execute(action: SharedTools::Tools::DiskTool::Action::FILE_CREATE, path: "./demo.rb")
|
|
12
|
+
# disk.execute(action: SharedTools::Tools::DiskTool::Action::FILE_WRITE, path: "./demo.rb", text: "puts 'Hello'")
|
|
13
|
+
# disk.execute(action: SharedTools::Tools::DiskTool::Action::FILE_READ, path: "./demo.rb")
|
|
14
|
+
# disk.execute(action: SharedTools::Tools::DiskTool::Action::FILE_DELETE, path: "./demo.rb")
|
|
15
|
+
class DiskTool < ::RubyLLM::Tool
|
|
16
|
+
def self.name = 'disk_tool'
|
|
17
|
+
description <<~TEXT
|
|
18
|
+
A tool for interacting with a system. It is able to list, create, delete, move and modify directories and files.
|
|
19
|
+
TEXT
|
|
20
|
+
|
|
21
|
+
module Action
|
|
22
|
+
DIRECTORY_CREATE = "directory_create"
|
|
23
|
+
DIRECTORY_DELETE = "directory_delete"
|
|
24
|
+
DIRECTORY_MOVE = "directory_move"
|
|
25
|
+
DIRECTORY_LIST = "directory_list"
|
|
26
|
+
FILE_CREATE = "file_create"
|
|
27
|
+
FILE_DELETE = "file_delete"
|
|
28
|
+
FILE_MOVE = "file_move"
|
|
29
|
+
FILE_READ = "file_read"
|
|
30
|
+
FILE_WRITE = "file_write"
|
|
31
|
+
FILE_REPLACE = "file_replace"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
ACTIONS = [
|
|
35
|
+
Action::DIRECTORY_CREATE,
|
|
36
|
+
Action::DIRECTORY_DELETE,
|
|
37
|
+
Action::DIRECTORY_MOVE,
|
|
38
|
+
Action::DIRECTORY_LIST,
|
|
39
|
+
Action::FILE_CREATE,
|
|
40
|
+
Action::FILE_DELETE,
|
|
41
|
+
Action::FILE_MOVE,
|
|
42
|
+
Action::FILE_READ,
|
|
43
|
+
Action::FILE_WRITE,
|
|
44
|
+
Action::FILE_REPLACE,
|
|
45
|
+
].freeze
|
|
46
|
+
|
|
47
|
+
params do
|
|
48
|
+
string :action, description: <<~TEXT.strip
|
|
49
|
+
Options:
|
|
50
|
+
* `#{Action::DIRECTORY_CREATE}`: creates a directory at a specific `path`
|
|
51
|
+
* `#{Action::DIRECTORY_DELETE}`: deletes a directory at a specific `path`
|
|
52
|
+
* `#{Action::DIRECTORY_MOVE}`: moves a directory from `path` to (`to`)
|
|
53
|
+
* `#{Action::DIRECTORY_LIST}`: lists the contents of a directory at a specific `path` (use '.' for root)
|
|
54
|
+
* `#{Action::FILE_CREATE}`: creates a file at a specific `path`
|
|
55
|
+
* `#{Action::FILE_DELETE}`: deletes a file at a specific `path`
|
|
56
|
+
* `#{Action::FILE_MOVE}`: moves a file from `path` to another
|
|
57
|
+
* `#{Action::FILE_READ}`: reads the contents of a file at a specific path
|
|
58
|
+
* `#{Action::FILE_WRITE}`: writes the contents of a file at a specific path
|
|
59
|
+
* `#{Action::FILE_REPLACE}`: replaces the contents of a file at a specific path
|
|
60
|
+
TEXT
|
|
61
|
+
|
|
62
|
+
string :path, description: <<~TEXT.strip
|
|
63
|
+
A file or directory path that is required for the following actions:
|
|
64
|
+
* `#{Action::DIRECTORY_CREATE}`
|
|
65
|
+
* `#{Action::DIRECTORY_DELETE}`
|
|
66
|
+
* `#{Action::DIRECTORY_MOVE}`
|
|
67
|
+
* `#{Action::DIRECTORY_LIST}`
|
|
68
|
+
* `#{Action::FILE_DELETE}`
|
|
69
|
+
* `#{Action::FILE_READ}`
|
|
70
|
+
* `#{Action::FILE_WRITE}`
|
|
71
|
+
* `#{Action::FILE_REPLACE}`
|
|
72
|
+
TEXT
|
|
73
|
+
|
|
74
|
+
string :destination, description: <<~TEXT.strip, required: false
|
|
75
|
+
A file or directory path that is required for the following actions:
|
|
76
|
+
* `#{Action::DIRECTORY_MOVE}`
|
|
77
|
+
* `#{Action::FILE_MOVE}`
|
|
78
|
+
TEXT
|
|
79
|
+
|
|
80
|
+
string :text, description: <<~TEXT.strip, required: false
|
|
81
|
+
The text to be written to a file for the `#{Action::FILE_WRITE}` action.
|
|
82
|
+
TEXT
|
|
83
|
+
|
|
84
|
+
string :old_text, description: <<~TEXT.strip, required: false
|
|
85
|
+
The old text to be replaced in a file for the `#{Action::FILE_REPLACE}` action.
|
|
86
|
+
TEXT
|
|
87
|
+
|
|
88
|
+
string :new_text, description: <<~TEXT.strip, required: false
|
|
89
|
+
The new text to replace in a few file for the `#{Action::FILE_REPLACE}` action.
|
|
90
|
+
TEXT
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# @param driver [SharedTools::Tools::Disk::BaseDriver] optional, defaults to LocalDriver with current directory
|
|
95
|
+
# @param logger [Logger] optional logger
|
|
96
|
+
def initialize(driver: nil, logger: nil)
|
|
97
|
+
@driver = driver || Disk::LocalDriver.new(root: Dir.pwd)
|
|
98
|
+
@logger = logger || RubyLLM.logger
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @param action [String]
|
|
102
|
+
# @param path [String]
|
|
103
|
+
# @param destination [String] optional
|
|
104
|
+
# @param old_text [String] optional
|
|
105
|
+
# @param new_text [String] optional
|
|
106
|
+
# @param text [String] optional
|
|
107
|
+
def execute(action:, path:, destination: nil, old_text: nil, new_text: nil, text: nil)
|
|
108
|
+
@logger.info({
|
|
109
|
+
action:,
|
|
110
|
+
path:,
|
|
111
|
+
destination:,
|
|
112
|
+
old_text:,
|
|
113
|
+
new_text:,
|
|
114
|
+
text:,
|
|
115
|
+
}.compact.map { |key, value| "#{key}=#{value.inspect}" }.join(" "))
|
|
116
|
+
|
|
117
|
+
case action
|
|
118
|
+
when Action::DIRECTORY_CREATE then @driver.directory_create(path:)
|
|
119
|
+
when Action::DIRECTORY_DELETE then @driver.directory_delete(path:)
|
|
120
|
+
when Action::DIRECTORY_MOVE then @driver.directory_move(path:, destination:)
|
|
121
|
+
when Action::DIRECTORY_LIST then @driver.directory_list(path:)
|
|
122
|
+
when Action::FILE_CREATE then @driver.file_create(path:)
|
|
123
|
+
when Action::FILE_DELETE then @driver.file_delete(path:)
|
|
124
|
+
when Action::FILE_MOVE then @driver.file_move(path:, destination:)
|
|
125
|
+
when Action::FILE_READ then @driver.file_read(path:)
|
|
126
|
+
when Action::FILE_WRITE then @driver.file_write(path:, text:)
|
|
127
|
+
when Action::FILE_REPLACE then @driver.file_replace(old_text:, new_text:, path:)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# Credit: https://max.engineer/giant-pdf-llm
|
|
3
|
+
|
|
4
|
+
begin
|
|
5
|
+
require "pdf-reader"
|
|
6
|
+
rescue LoadError
|
|
7
|
+
# pdf-reader is optional - will raise error when tool is used without it
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module SharedTools
|
|
11
|
+
module Tools
|
|
12
|
+
module Doc
|
|
13
|
+
# @example
|
|
14
|
+
# tool = SharedTools::Tools::Doc::PdfReaderTool.new
|
|
15
|
+
# tool.execute(doc_path: "./document.pdf", page_numbers: "1, 5, 10")
|
|
16
|
+
class PdfReaderTool < ::RubyLLM::Tool
|
|
17
|
+
def self.name = 'doc_pdf_read'
|
|
18
|
+
|
|
19
|
+
description "Read the text of any set of pages from a PDF document."
|
|
20
|
+
|
|
21
|
+
params do
|
|
22
|
+
string :page_numbers, description: 'Comma-separated page numbers (first page: 1). (e.g. "12, 14, 15")'
|
|
23
|
+
string :doc_path, description: "Path to the PDF document."
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param logger [Logger] optional logger
|
|
27
|
+
def initialize(logger: nil)
|
|
28
|
+
@logger = logger || RubyLLM.logger
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param page_numbers [String] comma-separated page numbers
|
|
32
|
+
# @param doc_path [String] path to PDF file
|
|
33
|
+
#
|
|
34
|
+
# @return [Hash] extraction result
|
|
35
|
+
def execute(page_numbers:, doc_path:)
|
|
36
|
+
raise LoadError, "PdfReaderTool requires the 'pdf-reader' gem. Install it with: gem install pdf-reader" unless defined?(PDF::Reader)
|
|
37
|
+
|
|
38
|
+
@logger.info("Reading PDF: #{doc_path}, pages: #{page_numbers}")
|
|
39
|
+
|
|
40
|
+
begin
|
|
41
|
+
@doc ||= PDF::Reader.new(doc_path)
|
|
42
|
+
@logger.debug("PDF loaded successfully, total pages: #{@doc.pages.size}")
|
|
43
|
+
|
|
44
|
+
page_numbers = page_numbers.split(",").map { |num| num.strip.to_i }
|
|
45
|
+
@logger.debug("Processing pages: #{page_numbers.join(", ")}")
|
|
46
|
+
|
|
47
|
+
# Validate page numbers
|
|
48
|
+
total_pages = @doc.pages.size
|
|
49
|
+
invalid_pages = page_numbers.select { |num| num < 1 || num > total_pages }
|
|
50
|
+
|
|
51
|
+
if invalid_pages.any?
|
|
52
|
+
@logger.warn("Invalid page numbers requested: #{invalid_pages.join(", ")}. Document has #{total_pages} pages.")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Filter valid pages and map to content
|
|
56
|
+
valid_pages = page_numbers.select { |num| num >= 1 && num <= total_pages }
|
|
57
|
+
pages = valid_pages.map { |num| [num, @doc.pages[num.to_i - 1]] }
|
|
58
|
+
|
|
59
|
+
result = {
|
|
60
|
+
total_pages: total_pages,
|
|
61
|
+
requested_pages: page_numbers,
|
|
62
|
+
invalid_pages: invalid_pages,
|
|
63
|
+
pages: pages.map { |num, p|
|
|
64
|
+
@logger.debug("Extracted text from page #{num} (#{p&.text&.bytesize || 0} bytes)")
|
|
65
|
+
{ page: num, text: p&.text }
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@logger.info("Successfully extracted #{pages.size} pages from PDF")
|
|
70
|
+
result
|
|
71
|
+
rescue => e
|
|
72
|
+
@logger.error("Failed to read PDF '#{doc_path}': #{e.message}")
|
|
73
|
+
{ error: e.message }
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../shared_tools'
|
|
4
|
+
|
|
5
|
+
module SharedTools
|
|
6
|
+
module Tools
|
|
7
|
+
# A tool for reading and processing documents
|
|
8
|
+
class DocTool < ::RubyLLM::Tool
|
|
9
|
+
def self.name = 'doc_tool'
|
|
10
|
+
|
|
11
|
+
module Action
|
|
12
|
+
PDF_READ = "pdf_read"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
ACTIONS = [
|
|
16
|
+
Action::PDF_READ,
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
description <<~TEXT
|
|
20
|
+
Read and process various document formats.
|
|
21
|
+
|
|
22
|
+
## Actions:
|
|
23
|
+
|
|
24
|
+
1. `#{Action::PDF_READ}` - Read specific pages from a PDF document
|
|
25
|
+
Required: "action": "pdf_read", "doc_path": "[path to PDF]", "page_numbers": "[comma-separated page numbers]"
|
|
26
|
+
|
|
27
|
+
The page_numbers parameter accepts:
|
|
28
|
+
- Single page: "5"
|
|
29
|
+
- Multiple pages: "1, 3, 5"
|
|
30
|
+
- Range notation: "1-10" or "1, 3-5, 10"
|
|
31
|
+
|
|
32
|
+
## Examples:
|
|
33
|
+
|
|
34
|
+
Read single page from PDF
|
|
35
|
+
{"action": "#{Action::PDF_READ}", "doc_path": "./document.pdf", "page_numbers": "1"}
|
|
36
|
+
|
|
37
|
+
Read multiple pages
|
|
38
|
+
{"action": "#{Action::PDF_READ}", "doc_path": "./report.pdf", "page_numbers": "1, 5, 10"}
|
|
39
|
+
|
|
40
|
+
Read page range
|
|
41
|
+
{"action": "#{Action::PDF_READ}", "doc_path": "./book.pdf", "page_numbers": "10-15"}
|
|
42
|
+
|
|
43
|
+
Read specific pages with range
|
|
44
|
+
{"action": "#{Action::PDF_READ}", "doc_path": "./manual.pdf", "page_numbers": "1, 5-8, 15, 20-25"}
|
|
45
|
+
TEXT
|
|
46
|
+
|
|
47
|
+
params do
|
|
48
|
+
string :action, description: <<~TEXT.strip
|
|
49
|
+
The document action to perform. Options:
|
|
50
|
+
* `#{Action::PDF_READ}`: Read pages from a PDF document
|
|
51
|
+
TEXT
|
|
52
|
+
|
|
53
|
+
string :doc_path, description: <<~TEXT.strip, required: false
|
|
54
|
+
Path to the document file. Required for the following actions:
|
|
55
|
+
* `#{Action::PDF_READ}`
|
|
56
|
+
TEXT
|
|
57
|
+
|
|
58
|
+
string :page_numbers, description: <<~TEXT.strip, required: false
|
|
59
|
+
Comma-separated page numbers to read (first page is 1).
|
|
60
|
+
Examples: "1", "1, 3, 5", "1-10", "1, 5-8, 15"
|
|
61
|
+
Required for the following actions:
|
|
62
|
+
* `#{Action::PDF_READ}`
|
|
63
|
+
TEXT
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @param logger [Logger] optional logger
|
|
67
|
+
def initialize(logger: nil)
|
|
68
|
+
@logger = logger || RubyLLM.logger
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @param action [String] the action to perform
|
|
72
|
+
# @param doc_path [String, nil] path to document
|
|
73
|
+
# @param page_numbers [String, nil] page numbers to read
|
|
74
|
+
#
|
|
75
|
+
# @return [Hash] execution result
|
|
76
|
+
def execute(action:, doc_path: nil, page_numbers: nil)
|
|
77
|
+
@logger.info("DocTool#execute action=#{action}")
|
|
78
|
+
|
|
79
|
+
case action.to_s.downcase
|
|
80
|
+
when Action::PDF_READ
|
|
81
|
+
require_param!(:doc_path, doc_path)
|
|
82
|
+
require_param!(:page_numbers, page_numbers)
|
|
83
|
+
pdf_reader_tool.execute(doc_path: doc_path, page_numbers: page_numbers)
|
|
84
|
+
else
|
|
85
|
+
{ error: "Unsupported action: #{action}. Supported actions are: #{ACTIONS.join(', ')}" }
|
|
86
|
+
end
|
|
87
|
+
rescue StandardError => e
|
|
88
|
+
@logger.error("DocTool execution failed: #{e.message}")
|
|
89
|
+
{ error: e.message }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
# @param name [Symbol]
|
|
95
|
+
# @param value [Object]
|
|
96
|
+
#
|
|
97
|
+
# @raise [ArgumentError]
|
|
98
|
+
# @return [void]
|
|
99
|
+
def require_param!(name, value)
|
|
100
|
+
raise ArgumentError, "#{name} param is required for this action" if value.nil?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @return [Doc::PdfReaderTool]
|
|
104
|
+
def pdf_reader_tool
|
|
105
|
+
@pdf_reader_tool ||= Doc::PdfReaderTool.new(logger: @logger)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|