shared_tools 0.4.2 → 0.4.4
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 +12 -0
- data/lib/shared_tools/tools/calculator_tool.rb +11 -1
- data/lib/shared_tools/tools/database_query_tool.rb +11 -1
- data/lib/shared_tools/tools/eval/shell_eval_tool.rb +26 -5
- data/lib/shared_tools/tools/search_codebase_tool.rb +91 -0
- data/lib/shared_tools/tools/weather_tool.rb +11 -1
- data/lib/shared_tools/version.rb +1 -1
- metadata +20 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 679fe640e745e716a1ec2c9cb77fd9af47b67b09ddd5caba23ef9b6fbbf50d24
|
|
4
|
+
data.tar.gz: afa308531e5afc205b244771db0ed2e8965e5532d5f642d81c3284819db6f7b6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 87264d8b31d6b016e56ac361fa528a73071163f4dfaa4d562e877e20b7d011aa85a0de033212aa326fe9e8983940893d046237eab043c5ee6f61306dc126c21b
|
|
7
|
+
data.tar.gz: eee3b6364a669539ff7d1ffb290e1255416c0a20f32f68873a4d69061dcf41e5675a23b2ecb3fcacaf7714f6ad0b69d726cf3577642bceef611895574870d44c
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
### [0.4.4] - 2026-06-06
|
|
6
|
+
|
|
7
|
+
#### Tools
|
|
8
|
+
- Added `SearchCodebaseTool` (`tools/search_codebase_tool.rb`) — read-only codebase search using ripgrep (`rg`) with grep fallback; supports `path`, `extension`, and `max_results` parameters; returns matches with file path and line number; no authorization prompt required
|
|
9
|
+
- Enhanced `ShellEvalTool`: added optional `workdir` parameter to set the working directory for command execution; validates the directory exists before running
|
|
10
|
+
- Enhanced `ShellEvalTool`: output is now truncated at 5000 bytes (stdout) and 2000 bytes (stderr) to prevent oversized tool responses; truncated output is marked with `[truncated]`
|
|
11
|
+
- Moved `require "open3"` to the top of `ShellEvalTool` instead of inline inside `execute`
|
|
12
|
+
|
|
13
|
+
#### Tests
|
|
14
|
+
- Added `test/shared_tools/tools/search_codebase_tool_test.rb` — 16 tests covering search, extension filtering, `max_results` capping, truncation flag, error cases, and tool-key reporting
|
|
15
|
+
- Added 4 tests to `test/shared_tools/tools/eval/shell_eval_tool_test.rb` covering `workdir` execution, missing-directory error, stdout truncation, and non-truncation of short output
|
|
16
|
+
|
|
5
17
|
### [0.4.2] - 2026-03-27
|
|
6
18
|
|
|
7
19
|
#### MCP Clients
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
# calculator_tool.rb - Safe mathematical calculator
|
|
2
2
|
require 'ruby_llm/tool'
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
DENTAKU_AVAILABLE = begin
|
|
5
|
+
require 'dentaku'
|
|
6
|
+
true
|
|
7
|
+
rescue LoadError, Gem::LoadError
|
|
8
|
+
false
|
|
9
|
+
end
|
|
4
10
|
|
|
5
11
|
module SharedTools
|
|
6
12
|
module Tools
|
|
7
13
|
class CalculatorTool < RubyLLM::Tool
|
|
8
14
|
def self.name = 'calculator'
|
|
9
15
|
|
|
16
|
+
def available?
|
|
17
|
+
DENTAKU_AVAILABLE
|
|
18
|
+
end
|
|
19
|
+
|
|
10
20
|
description <<~'DESCRIPTION'
|
|
11
21
|
Perform advanced mathematical calculations with comprehensive error handling and validation.
|
|
12
22
|
This tool supports basic arithmetic operations, parentheses, and common mathematical functions.
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
# database_query_tool.rb - Safe database query execution
|
|
2
2
|
require 'ruby_llm/tool'
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
SEQUEL_AVAILABLE = begin
|
|
5
|
+
require 'sequel'
|
|
6
|
+
true
|
|
7
|
+
rescue LoadError, Gem::LoadError
|
|
8
|
+
false
|
|
9
|
+
end
|
|
4
10
|
|
|
5
11
|
module SharedTools
|
|
6
12
|
module Tools
|
|
7
13
|
class DatabaseQueryTool < RubyLLM::Tool
|
|
8
14
|
def self.name = 'database_query'
|
|
9
15
|
|
|
16
|
+
def available?
|
|
17
|
+
SEQUEL_AVAILABLE
|
|
18
|
+
end
|
|
19
|
+
|
|
10
20
|
description <<~'DESCRIPTION'
|
|
11
21
|
Execute safe, read-only database queries with automatic connection management and security controls.
|
|
12
22
|
This tool is designed for secure data retrieval operations only, restricting access to SELECT statements
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "open3"
|
|
3
4
|
|
|
4
5
|
module SharedTools
|
|
5
6
|
module Tools
|
|
@@ -7,13 +8,18 @@ module SharedTools
|
|
|
7
8
|
# @example
|
|
8
9
|
# tool = SharedTools::Tools::Eval::ShellEvalTool.new
|
|
9
10
|
# tool.execute(command: "ls -la")
|
|
11
|
+
# tool.execute(command: "ls -la", workdir: "/tmp")
|
|
10
12
|
class ShellEvalTool < ::RubyLLM::Tool
|
|
13
|
+
STDOUT_MAX = 5000
|
|
14
|
+
STDERR_MAX = 2000
|
|
15
|
+
|
|
11
16
|
def self.name = 'eval_shell'
|
|
12
17
|
|
|
13
18
|
description "Execute a shell command safely and return the result."
|
|
14
19
|
|
|
15
20
|
params do
|
|
16
21
|
string :command, description: "The shell command to execute"
|
|
22
|
+
string :workdir, description: "Working directory for the command (default: current directory)", required: false
|
|
17
23
|
end
|
|
18
24
|
|
|
19
25
|
# @param logger [Logger] optional logger
|
|
@@ -22,9 +28,10 @@ module SharedTools
|
|
|
22
28
|
end
|
|
23
29
|
|
|
24
30
|
# @param command [String] shell command to execute
|
|
31
|
+
# @param workdir [String, nil] working directory for the command
|
|
25
32
|
#
|
|
26
33
|
# @return [Hash] execution result
|
|
27
|
-
def execute(command:)
|
|
34
|
+
def execute(command:, workdir: nil)
|
|
28
35
|
@logger.info("Requesting permission to execute command: '#{command}'")
|
|
29
36
|
|
|
30
37
|
if command.strip.empty?
|
|
@@ -33,7 +40,12 @@ module SharedTools
|
|
|
33
40
|
return { error: error_msg }
|
|
34
41
|
end
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
if workdir
|
|
44
|
+
unless File.directory?(workdir)
|
|
45
|
+
return { error: "Working directory not found: #{workdir}" }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
37
49
|
allowed = SharedTools.execute?(tool: self.class.to_s, stuff: command)
|
|
38
50
|
|
|
39
51
|
unless allowed
|
|
@@ -43,9 +55,11 @@ module SharedTools
|
|
|
43
55
|
|
|
44
56
|
@logger.info("Executing command: '#{command}'")
|
|
45
57
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
opts = workdir ? { chdir: workdir } : {}
|
|
59
|
+
stdout, stderr, status = Open3.capture3(command, **opts)
|
|
60
|
+
|
|
61
|
+
stdout = truncate(stdout, STDOUT_MAX)
|
|
62
|
+
stderr = truncate(stderr, STDERR_MAX)
|
|
49
63
|
|
|
50
64
|
if status.success?
|
|
51
65
|
@logger.debug("Command execution completed successfully with #{stdout.bytesize} bytes of output")
|
|
@@ -58,6 +72,13 @@ module SharedTools
|
|
|
58
72
|
@logger.error("Command execution failed: #{e.message}")
|
|
59
73
|
{ error: e.message }
|
|
60
74
|
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def truncate(str, max)
|
|
79
|
+
return str if str.length <= max
|
|
80
|
+
str.slice(0, max) + "\n[truncated]"
|
|
81
|
+
end
|
|
61
82
|
end
|
|
62
83
|
end
|
|
63
84
|
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module SharedTools
|
|
6
|
+
module Tools
|
|
7
|
+
# Search for a term across files using ripgrep (rg) with grep as fallback.
|
|
8
|
+
# Read-only — no authorization prompt required.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# tool = SharedTools::Tools::SearchCodebaseTool.new
|
|
12
|
+
# tool.execute(term: "def execute")
|
|
13
|
+
# tool.execute(term: "RubyLLM", extension: "rb", max_results: 20)
|
|
14
|
+
class SearchCodebaseTool < ::RubyLLM::Tool
|
|
15
|
+
MAX_RESULTS_CAP = 500
|
|
16
|
+
|
|
17
|
+
def self.name = 'search_codebase'
|
|
18
|
+
|
|
19
|
+
description "Search for a term or pattern across files. Uses ripgrep (rg) when available, falls back to grep. Returns matching lines with file path and line number."
|
|
20
|
+
|
|
21
|
+
params do
|
|
22
|
+
string :term, description: "The search term or regex pattern to find"
|
|
23
|
+
string :path, description: "Directory to search in (default: current directory)", required: false
|
|
24
|
+
string :extension, description: "File extension to restrict the search to, without the dot (e.g. 'rb', 'js')", required: false
|
|
25
|
+
integer :max_results, description: "Maximum number of matching lines to return (default: 50, max: 500)", required: false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def initialize(logger: nil)
|
|
29
|
+
@logger = logger || RubyLLM.logger
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def execute(term:, path: ".", extension: nil, max_results: 50)
|
|
33
|
+
@logger.info("SearchCodebaseTool#execute term=#{term.inspect} path=#{path} extension=#{extension.inspect}")
|
|
34
|
+
|
|
35
|
+
return { error: "Search term cannot be empty" } if term.strip.empty?
|
|
36
|
+
|
|
37
|
+
max_results = [[max_results.to_i, 1].max, MAX_RESULTS_CAP].min
|
|
38
|
+
search_path = File.expand_path(path)
|
|
39
|
+
|
|
40
|
+
return { error: "Path not found: #{path}" } unless File.exist?(search_path)
|
|
41
|
+
|
|
42
|
+
stdout, _stderr, _status = run_search(term: term, path: search_path, extension: extension)
|
|
43
|
+
|
|
44
|
+
all_lines = stdout.lines.map(&:chomp).reject(&:empty?)
|
|
45
|
+
matches = all_lines.first(max_results)
|
|
46
|
+
truncated = all_lines.size > max_results
|
|
47
|
+
|
|
48
|
+
{
|
|
49
|
+
matches: matches,
|
|
50
|
+
count: matches.size,
|
|
51
|
+
truncated: truncated,
|
|
52
|
+
tool: ripgrep_available? ? "rg" : "grep"
|
|
53
|
+
}
|
|
54
|
+
rescue => e
|
|
55
|
+
@logger.error("SearchCodebaseTool error: #{e.message}")
|
|
56
|
+
{ error: e.message }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def run_search(term:, path:, extension:)
|
|
62
|
+
if ripgrep_available?
|
|
63
|
+
run_ripgrep(term: term, path: path, extension: extension)
|
|
64
|
+
else
|
|
65
|
+
run_grep(term: term, path: path, extension: extension)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def run_ripgrep(term:, path:, extension:)
|
|
70
|
+
args = ["rg", "--no-heading", "-n"]
|
|
71
|
+
args += ["-g", "*.#{extension}"] if extension
|
|
72
|
+
args << term
|
|
73
|
+
args << path
|
|
74
|
+
Open3.capture3(*args)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def run_grep(term:, path:, extension:)
|
|
78
|
+
args = ["grep", "-r", "-n"]
|
|
79
|
+
args += ["--include=*.#{extension}"] if extension
|
|
80
|
+
args << term
|
|
81
|
+
args << path
|
|
82
|
+
Open3.capture3(*args)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def ripgrep_available?
|
|
86
|
+
return @ripgrep_available unless @ripgrep_available.nil?
|
|
87
|
+
@ripgrep_available = system("which", "rg", out: File::NULL, err: File::NULL)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
# weather_tool.rb - API integration example
|
|
2
2
|
require 'ruby_llm/tool'
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
OPENWEATHERMAP_AVAILABLE = begin
|
|
5
|
+
require 'openweathermap'
|
|
6
|
+
true
|
|
7
|
+
rescue LoadError, Gem::LoadError
|
|
8
|
+
false
|
|
9
|
+
end
|
|
4
10
|
|
|
5
11
|
module SharedTools
|
|
6
12
|
module Tools
|
|
7
13
|
class WeatherTool < RubyLLM::Tool
|
|
8
14
|
def self.name = 'weather_tool'
|
|
9
15
|
|
|
16
|
+
def available?
|
|
17
|
+
OPENWEATHERMAP_AVAILABLE
|
|
18
|
+
end
|
|
19
|
+
|
|
10
20
|
description <<~'DESCRIPTION'
|
|
11
21
|
Retrieve comprehensive current weather information for any city worldwide using the OpenWeatherMap API.
|
|
12
22
|
This tool provides real-time weather data including temperature, atmospheric conditions, humidity,
|
data/lib/shared_tools/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: shared_tools
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dewayne VanHoozer
|
|
@@ -65,6 +65,20 @@ dependencies:
|
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: bigdecimal
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '4.0'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '4.0'
|
|
68
82
|
- !ruby/object:Gem::Dependency
|
|
69
83
|
name: dentaku
|
|
70
84
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -72,7 +86,7 @@ dependencies:
|
|
|
72
86
|
- - ">="
|
|
73
87
|
- !ruby/object:Gem::Version
|
|
74
88
|
version: '0'
|
|
75
|
-
type: :
|
|
89
|
+
type: :development
|
|
76
90
|
prerelease: false
|
|
77
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
92
|
requirements:
|
|
@@ -86,7 +100,7 @@ dependencies:
|
|
|
86
100
|
- - ">="
|
|
87
101
|
- !ruby/object:Gem::Version
|
|
88
102
|
version: '0'
|
|
89
|
-
type: :
|
|
103
|
+
type: :development
|
|
90
104
|
prerelease: false
|
|
91
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
92
106
|
requirements:
|
|
@@ -100,7 +114,7 @@ dependencies:
|
|
|
100
114
|
- - ">="
|
|
101
115
|
- !ruby/object:Gem::Version
|
|
102
116
|
version: '0'
|
|
103
|
-
type: :
|
|
117
|
+
type: :development
|
|
104
118
|
prerelease: false
|
|
105
119
|
version_requirements: !ruby/object:Gem::Requirement
|
|
106
120
|
requirements:
|
|
@@ -385,6 +399,7 @@ files:
|
|
|
385
399
|
- lib/shared_tools/tools/notification/mac_driver.rb
|
|
386
400
|
- lib/shared_tools/tools/notification/null_driver.rb
|
|
387
401
|
- lib/shared_tools/tools/notification_tool.rb
|
|
402
|
+
- lib/shared_tools/tools/search_codebase_tool.rb
|
|
388
403
|
- lib/shared_tools/tools/secure_tool_template.rb
|
|
389
404
|
- lib/shared_tools/tools/system_info_tool.rb
|
|
390
405
|
- lib/shared_tools/tools/version.rb
|
|
@@ -415,7 +430,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
415
430
|
- !ruby/object:Gem::Version
|
|
416
431
|
version: '0'
|
|
417
432
|
requirements: []
|
|
418
|
-
rubygems_version: 4.0.
|
|
433
|
+
rubygems_version: 4.0.13
|
|
419
434
|
specification_version: 4
|
|
420
435
|
summary: Shared utilities and AI tools for Ruby applications with configurable logging
|
|
421
436
|
test_files: []
|