shared_tools 0.3.1 → 0.4.1

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -16
  3. data/README.md +257 -262
  4. data/lib/shared_tools/browser_tool.rb +5 -0
  5. data/lib/shared_tools/calculator_tool.rb +4 -0
  6. data/lib/shared_tools/clipboard_tool.rb +4 -0
  7. data/lib/shared_tools/composite_analysis_tool.rb +4 -0
  8. data/lib/shared_tools/computer_tool.rb +5 -0
  9. data/lib/shared_tools/cron_tool.rb +4 -0
  10. data/lib/shared_tools/current_date_time_tool.rb +4 -0
  11. data/lib/shared_tools/data_science_kit.rb +4 -0
  12. data/lib/shared_tools/database.rb +4 -0
  13. data/lib/shared_tools/database_query_tool.rb +4 -0
  14. data/lib/shared_tools/database_tool.rb +5 -0
  15. data/lib/shared_tools/disk_tool.rb +5 -0
  16. data/lib/shared_tools/dns_tool.rb +4 -0
  17. data/lib/shared_tools/doc_tool.rb +5 -0
  18. data/lib/shared_tools/error_handling_tool.rb +4 -0
  19. data/lib/shared_tools/eval_tool.rb +5 -0
  20. data/lib/shared_tools/mcp/brave_search_client.rb +37 -0
  21. data/lib/shared_tools/mcp/chart_client.rb +32 -0
  22. data/lib/shared_tools/mcp/github_client.rb +38 -0
  23. data/lib/shared_tools/mcp/hugging_face_client.rb +43 -0
  24. data/lib/shared_tools/mcp/memory_client.rb +33 -0
  25. data/lib/shared_tools/mcp/notion_client.rb +40 -0
  26. data/lib/shared_tools/mcp/sequential_thinking_client.rb +33 -0
  27. data/lib/shared_tools/mcp/slack_client.rb +54 -0
  28. data/lib/shared_tools/mcp/streamable_http_patch.rb +42 -0
  29. data/lib/shared_tools/mcp/tavily_client.rb +41 -0
  30. data/lib/shared_tools/mcp.rb +45 -16
  31. data/lib/shared_tools/system_info_tool.rb +4 -0
  32. data/lib/shared_tools/tools/browser/base_tool.rb +8 -12
  33. data/lib/shared_tools/tools/browser/click_tool.rb +4 -2
  34. data/lib/shared_tools/tools/browser/ferrum_driver.rb +119 -0
  35. data/lib/shared_tools/tools/browser/inspect_tool.rb +4 -2
  36. data/lib/shared_tools/tools/browser/page_inspect_tool.rb +4 -2
  37. data/lib/shared_tools/tools/browser/page_screenshot_tool.rb +19 -7
  38. data/lib/shared_tools/tools/browser/selector_inspect_tool.rb +4 -2
  39. data/lib/shared_tools/tools/browser/text_field_area_set_tool.rb +4 -2
  40. data/lib/shared_tools/tools/browser/visit_tool.rb +4 -2
  41. data/lib/shared_tools/tools/browser.rb +31 -2
  42. data/lib/shared_tools/tools/browser_tool.rb +6 -0
  43. data/lib/shared_tools/tools/clipboard_tool.rb +69 -144
  44. data/lib/shared_tools/tools/composite_analysis_tool.rb +60 -4
  45. data/lib/shared_tools/tools/computer/mac_driver.rb +37 -4
  46. data/lib/shared_tools/tools/cron_tool.rb +237 -379
  47. data/lib/shared_tools/tools/current_date_time_tool.rb +54 -120
  48. data/lib/shared_tools/tools/data_science_kit.rb +63 -13
  49. data/lib/shared_tools/tools/dns_tool.rb +335 -269
  50. data/lib/shared_tools/tools/doc/docx_reader_tool.rb +107 -0
  51. data/lib/shared_tools/tools/doc/spreadsheet_reader_tool.rb +171 -0
  52. data/lib/shared_tools/tools/doc/text_reader_tool.rb +57 -0
  53. data/lib/shared_tools/tools/doc.rb +3 -0
  54. data/lib/shared_tools/tools/doc_tool.rb +101 -6
  55. data/lib/shared_tools/tools/docker/compose_run_tool.rb +1 -1
  56. data/lib/shared_tools/tools/enabler.rb +42 -0
  57. data/lib/shared_tools/tools/error_handling_tool.rb +3 -1
  58. data/lib/shared_tools/tools/notification/base_driver.rb +51 -0
  59. data/lib/shared_tools/tools/notification/linux_driver.rb +115 -0
  60. data/lib/shared_tools/tools/notification/mac_driver.rb +66 -0
  61. data/lib/shared_tools/tools/notification/null_driver.rb +29 -0
  62. data/lib/shared_tools/tools/notification.rb +12 -0
  63. data/lib/shared_tools/tools/notification_tool.rb +99 -0
  64. data/lib/shared_tools/tools/system_info_tool.rb +130 -343
  65. data/lib/shared_tools/tools/workflow_manager_tool.rb +32 -0
  66. data/lib/shared_tools/utilities.rb +193 -0
  67. data/lib/shared_tools/version.rb +1 -1
  68. data/lib/shared_tools/weather_tool.rb +4 -0
  69. data/lib/shared_tools/workflow_manager_tool.rb +4 -0
  70. data/lib/shared_tools.rb +28 -38
  71. metadata +74 -9
  72. data/lib/shared_tools/mcp/github_mcp_server.rb +0 -58
  73. data/lib/shared_tools/mcp/imcp.rb +0 -28
  74. data/lib/shared_tools/mcp/tavily_mcp_server.rb +0 -44
  75. data/lib/shared_tools/tools/devops_toolkit.rb +0 -420
@@ -1,48 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ruby_llm/tool'
4
3
  require 'time'
4
+ require_relative '../../shared_tools'
5
5
 
6
6
  module SharedTools
7
7
  module Tools
8
- # A tool that returns the current date, time, and timezone information.
9
- # Useful for AI assistants that need to know the current time context.
8
+ # Returns the current date, time, and timezone from the local system.
10
9
  #
11
10
  # @example
12
11
  # tool = SharedTools::Tools::CurrentDateTimeTool.new
13
- # result = tool.execute
14
- # puts result[:date] # "2025-12-17"
15
- # puts result[:time] # "13:45:30"
16
- # puts result[:timezone] # "America/Chicago"
17
- class CurrentDateTimeTool < RubyLLM::Tool
18
- def self.name = 'current_date_time'
12
+ # tool.execute # full output
13
+ # tool.execute(format: 'date') # date fields only
14
+ class CurrentDateTimeTool < ::RubyLLM::Tool
15
+ def self.name = 'current_date_time_tool'
19
16
 
20
- description <<~'DESCRIPTION'
21
- Returns the current date, time, and timezone information from the system.
22
- This tool provides accurate temporal context for AI assistants that need
23
- to reason about time-sensitive information or schedule-related queries.
17
+ description <<~DESC
18
+ Returns the current date, time, timezone, and calendar metadata from the system clock.
24
19
 
25
- The tool returns:
26
- - Current date in ISO 8601 format (YYYY-MM-DD)
27
- - Current time in 24-hour format (HH:MM:SS)
28
- - Current timezone name and UTC offset
29
- - Unix timestamp for precise time calculations
30
- - Day of week and week number for scheduling context
31
-
32
- Example usage:
33
- tool = SharedTools::Tools::CurrentDateTimeTool.new
34
- result = tool.execute
35
- puts "Today is #{result[:day_of_week]}, #{result[:date]}"
36
- puts "Current time: #{result[:time]} #{result[:timezone]}"
37
- DESCRIPTION
20
+ Supported formats:
21
+ - 'full' (default) — all fields: date, time, timezone, ISO 8601, unix timestamp, DST flag
22
+ - 'date' — year, month, day, day_of_week, week_of_year, quarter, ordinal_day
23
+ - 'time' — hour, minute, second, timezone, utc_offset
24
+ - 'iso8601' iso8601, iso8601_utc, unix_timestamp
25
+ DESC
38
26
 
39
27
  params do
40
- string :format, description: <<~DESC.strip, required: false
41
- Output format preference. Options:
42
- - 'full' (default): Returns all date/time information
43
- - 'date_only': Returns only date-related fields
44
- - 'time_only': Returns only time-related fields
45
- - 'iso8601': Returns a single ISO 8601 formatted datetime string
28
+ string :format, required: false, description: <<~DESC.strip
29
+ Output format. Options: 'full' (default), 'date', 'time', 'iso8601'.
46
30
  DESC
47
31
  end
48
32
 
@@ -51,103 +35,53 @@ module SharedTools
51
35
  @logger = logger || RubyLLM.logger
52
36
  end
53
37
 
54
- # Execute the date/time query
55
- #
56
- # @param format [String] Output format ('full', 'date_only', 'time_only', 'iso8601')
57
- # @return [Hash] Current date/time information
38
+ # @param format [String] output format
39
+ # @return [Hash] date/time information
58
40
  def execute(format: 'full')
59
- @logger.info("DateTimeTool#execute format=#{format.inspect}")
41
+ @logger.info("CurrentDateTimeTool#execute format=#{format}")
60
42
 
61
43
  now = Time.now
62
-
63
- case format.to_s.downcase
64
- when 'date_only'
65
- date_only_response(now)
66
- when 'time_only'
67
- time_only_response(now)
68
- when 'iso8601'
69
- iso8601_response(now)
70
- else
71
- full_response(now)
72
- end
73
- end
74
-
75
- private
76
-
77
- # Full response with all date/time information
78
- def full_response(now)
79
- {
80
- success: true,
81
- date: now.strftime('%Y-%m-%d'),
82
- time: now.strftime('%H:%M:%S'),
83
- datetime: now.iso8601,
84
- timezone: now.zone,
85
- timezone_name: timezone_name(now),
86
- utc_offset: formatted_utc_offset(now),
87
- unix_timestamp: now.to_i,
88
- day_of_week: now.strftime('%A'),
89
- day_of_year: now.yday,
90
- week_number: now.strftime('%V').to_i,
91
- is_dst: now.dst?,
92
- quarter: ((now.month - 1) / 3) + 1
44
+ utc = now.utc
45
+
46
+ date_info = {
47
+ year: now.year,
48
+ month: now.month,
49
+ day: now.day,
50
+ day_of_week: now.strftime('%A'),
51
+ day_of_week_num: now.wday,
52
+ week_of_year: now.strftime('%U').to_i,
53
+ quarter: ((now.month - 1) / 3) + 1,
54
+ ordinal_day: now.yday
93
55
  }
94
- end
95
56
 
96
- # Date-only response
97
- def date_only_response(now)
98
- {
99
- success: true,
100
- date: now.strftime('%Y-%m-%d'),
101
- year: now.year,
102
- month: now.month,
103
- month_name: now.strftime('%B'),
104
- day: now.day,
105
- day_of_week: now.strftime('%A'),
106
- day_of_year: now.yday,
107
- week_number: now.strftime('%V').to_i,
108
- quarter: ((now.month - 1) / 3) + 1
57
+ time_info = {
58
+ hour: now.hour,
59
+ minute: now.min,
60
+ second: now.sec,
61
+ timezone: now.zone,
62
+ utc_offset: now.strftime('%z'),
63
+ utc_offset_hours: (now.utc_offset / 3600.0).round(2)
109
64
  }
110
- end
111
65
 
112
- # Time-only response
113
- def time_only_response(now)
114
- {
115
- success: true,
116
- time: now.strftime('%H:%M:%S'),
117
- time_12h: now.strftime('%I:%M:%S %p'),
118
- hour: now.hour,
119
- minute: now.min,
120
- second: now.sec,
121
- timezone: now.zone,
122
- timezone_name: timezone_name(now),
123
- utc_offset: formatted_utc_offset(now),
124
- unix_timestamp: now.to_i,
125
- is_dst: now.dst?
66
+ iso_info = {
67
+ iso8601: now.iso8601,
68
+ iso8601_utc: utc.iso8601,
69
+ unix_timestamp: now.to_i
126
70
  }
127
- end
128
-
129
- # ISO 8601 formatted response
130
- def iso8601_response(now)
131
- {
132
- success: true,
133
- datetime: now.iso8601,
134
- utc: now.utc.iso8601
135
- }
136
- end
137
-
138
- # Get the IANA timezone name if available
139
- def timezone_name(time)
140
- # Try to get IANA timezone from TZ environment variable
141
- ENV['TZ'] || time.zone
142
- end
143
71
 
144
- # Format UTC offset as "+HH:MM" or "-HH:MM"
145
- def formatted_utc_offset(time)
146
- offset_seconds = time.utc_offset
147
- sign = offset_seconds >= 0 ? '+' : '-'
148
- hours, remainder = offset_seconds.abs.divmod(3600)
149
- minutes = remainder / 60
150
- format('%s%02d:%02d', sign, hours, minutes)
72
+ case format.to_s.downcase
73
+ when 'date'
74
+ { success: true }.merge(date_info)
75
+ when 'time'
76
+ { success: true }.merge(time_info)
77
+ when 'iso8601'
78
+ { success: true }.merge(iso_info)
79
+ else
80
+ { success: true, dst: now.dst? }
81
+ .merge(date_info)
82
+ .merge(time_info)
83
+ .merge(iso_info)
84
+ end
151
85
  end
152
86
  end
153
87
  end
@@ -28,13 +28,22 @@ module SharedTools
28
28
  Each analysis type requires specific data formats and optional parameters.
29
29
  DESC
30
30
 
31
- string :data_source, description: <<~DESC.strip, required: true
31
+ string :data_source, description: <<~DESC.strip, required: false
32
32
  Data source specification for analysis. Can be:
33
33
  - File path: Relative or absolute path to CSV, JSON, Excel, or Parquet files
34
34
  - Database query: SQL SELECT statement for database-sourced data
35
35
  - API endpoint: HTTP URL for REST API data sources
36
36
  The tool automatically detects the format and applies appropriate parsing.
37
37
  Examples: './sales_data.csv', 'SELECT * FROM transactions', 'https://api.company.com/data'
38
+ Either data_source or data must be provided.
39
+ DESC
40
+
41
+ string :data, description: <<~DESC.strip, required: false
42
+ Inline data to analyse, provided directly as a JSON string. Accepted formats:
43
+ - Array of hashes: '[{"month":"Jan","value":42},{"month":"Feb","value":45}]'
44
+ - Pipe-delimited table string: "Col A | Col B\n1 | 2\n3 | 4"
45
+ - Comma-separated numbers (single series): "42,45,51,48,55" — parsed as [{value: n}]
46
+ Either data or data_source must be provided.
38
47
  DESC
39
48
 
40
49
  object :parameters, description: <<~DESC.strip, required: false do
@@ -82,11 +91,18 @@ module SharedTools
82
91
  @logger = logger || RubyLLM.logger
83
92
  end
84
93
 
85
- def execute(analysis_type:, data_source:, **parameters)
94
+ def execute(analysis_type:, data_source: nil, data: nil, **parameters)
86
95
  analysis_start = Time.now
87
96
 
88
97
  begin
89
- @logger.info("DataScienceKit#execute analysis_type=#{analysis_type} data_source=#{data_source}")
98
+ if data_source.nil? && data.nil?
99
+ return {
100
+ success: false,
101
+ error: "Either data_source or data must be provided."
102
+ }
103
+ end
104
+
105
+ @logger.info("DataScienceKit#execute analysis_type=#{analysis_type}")
90
106
 
91
107
  # Validate analysis type
92
108
  unless VALID_ANALYSIS_TYPES.include?(analysis_type)
@@ -99,21 +115,21 @@ module SharedTools
99
115
  end
100
116
 
101
117
  # Load and validate data
102
- data = load_data(data_source)
103
- validate_data_for_analysis(data, analysis_type, parameters)
118
+ loaded_data = data ? parse_inline_data(data) : load_data(data_source)
119
+ validate_data_for_analysis(loaded_data, analysis_type, parameters)
104
120
 
105
121
  # Perform analysis
106
122
  result = case analysis_type
107
123
  when "statistical_summary"
108
- generate_statistical_summary(data, parameters)
124
+ generate_statistical_summary(loaded_data, parameters)
109
125
  when "correlation_analysis"
110
- perform_correlation_analysis(data, parameters)
126
+ perform_correlation_analysis(loaded_data, parameters)
111
127
  when "time_series"
112
- analyze_time_series(data, parameters)
128
+ analyze_time_series(loaded_data, parameters)
113
129
  when "clustering"
114
- perform_clustering(data, parameters)
130
+ perform_clustering(loaded_data, parameters)
115
131
  when "prediction"
116
- generate_predictions(data, parameters)
132
+ generate_predictions(loaded_data, parameters)
117
133
  end
118
134
 
119
135
  analysis_duration = (Time.now - analysis_start).round(3)
@@ -123,7 +139,7 @@ module SharedTools
123
139
  success: true,
124
140
  analysis_type: analysis_type,
125
141
  result: result,
126
- data_summary: summarize_data(data),
142
+ data_summary: summarize_data(loaded_data),
127
143
  analyzed_at: Time.now.iso8601,
128
144
  duration_seconds: analysis_duration
129
145
  }
@@ -133,8 +149,7 @@ module SharedTools
133
149
  success: false,
134
150
  error: e.message,
135
151
  error_type: e.class.name,
136
- analysis_type: analysis_type,
137
- data_source: data_source
152
+ analysis_type: analysis_type
138
153
  }
139
154
  end
140
155
  end
@@ -189,6 +204,41 @@ module SharedTools
189
204
  end
190
205
  end
191
206
 
207
+ def parse_inline_data(raw)
208
+ raw = raw.strip
209
+ # JSON array or object
210
+ if raw.start_with?('{', '[')
211
+ begin
212
+ return JSON.parse(raw)
213
+ rescue JSON::ParserError
214
+ end
215
+ end
216
+ lines = raw.lines.map(&:strip).reject(&:empty?)
217
+ # Pipe-delimited table
218
+ if lines.first&.include?('|')
219
+ headers = lines.first.split('|').map(&:strip).reject(&:empty?)
220
+ data_lines = lines.drop(1).reject { |l| l.match?(/^\|?[-:\s|]+$/) }
221
+ return data_lines.map do |line|
222
+ values = line.split('|').map(&:strip).reject(&:empty?)
223
+ headers.zip(values).to_h
224
+ end
225
+ end
226
+ # CSV header row
227
+ if lines.size > 1 && lines.first.include?(',') && lines.first.match?(/[a-zA-Z]/)
228
+ headers = lines.first.split(',').map(&:strip)
229
+ return lines.drop(1).map do |line|
230
+ values = line.split(',').map(&:strip)
231
+ headers.zip(values).to_h
232
+ end
233
+ end
234
+ # Comma-separated numbers — single series
235
+ if lines.size == 1 && lines.first.match?(/^[\d.,\s]+$/)
236
+ return lines.first.split(',').map { |v| { "value" => v.strip.to_f } }
237
+ end
238
+ # Plain lines
239
+ lines.map { |l| { "value" => l } }
240
+ end
241
+
192
242
  # Generate sample data for testing
193
243
  def generate_sample_data(size = 30)
194
244
  (1..size).map do |i|