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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c72583fc2d491abce837a4c42268770fa5385c96800c28bd846a2b6cd12aa506
4
- data.tar.gz: 41aefa6cb69ceea26c66d2c68bc46602d08e824213737ad432bdcf73bf3fef92
3
+ metadata.gz: 679fe640e745e716a1ec2c9cb77fd9af47b67b09ddd5caba23ef9b6fbbf50d24
4
+ data.tar.gz: afa308531e5afc205b244771db0ed2e8965e5532d5f642d81c3284819db6f7b6
5
5
  SHA512:
6
- metadata.gz: 77eb4bb5382359ef3132b376ddeca5f244edf9f4e4eab2d54bf7c9a0eff49825c75a0fbf5efca5ce0f737b799c782d34f3c1f91ac3a1e42dbd9c7d14b6a3cb16
7
- data.tar.gz: e342c4d27481ce0bc72631753f64b6dfc047b70d3848b48be21affec7baa0360f46537dfbb86061cac756818df64f93e384c3f1e0048be83aee620ef1e585593
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
- require 'dentaku'
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
- require 'sequel'
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
- # Show user the command and ask for confirmation
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
- # Use Open3 for safer command execution with proper error handling
47
- require "open3"
48
- stdout, stderr, status = Open3.capture3(command)
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
- require 'openweathermap'
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,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SharedTools
4
- VERSION = "0.4.2"
4
+ VERSION = "0.4.4"
5
5
  end
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.2
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: :runtime
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: :runtime
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: :runtime
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.9
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: []