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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/README.md +594 -42
  4. data/lib/shared_tools/{ruby_llm/mcp → mcp}/github_mcp_server.rb +20 -3
  5. data/lib/shared_tools/mcp/imcp.rb +28 -0
  6. data/lib/shared_tools/mcp/tavily_mcp_server.rb +44 -0
  7. data/lib/shared_tools/mcp.rb +24 -0
  8. data/lib/shared_tools/tools/browser/base_driver.rb +64 -0
  9. data/lib/shared_tools/tools/browser/base_tool.rb +50 -0
  10. data/lib/shared_tools/tools/browser/click_tool.rb +54 -0
  11. data/lib/shared_tools/tools/browser/elements/element_grouper.rb +73 -0
  12. data/lib/shared_tools/tools/browser/elements/nearby_element_detector.rb +109 -0
  13. data/lib/shared_tools/tools/browser/formatters/action_formatter.rb +37 -0
  14. data/lib/shared_tools/tools/browser/formatters/data_entry_formatter.rb +135 -0
  15. data/lib/shared_tools/tools/browser/formatters/element_formatter.rb +52 -0
  16. data/lib/shared_tools/tools/browser/formatters/input_formatter.rb +59 -0
  17. data/lib/shared_tools/tools/browser/inspect_tool.rb +87 -0
  18. data/lib/shared_tools/tools/browser/inspect_utils.rb +51 -0
  19. data/lib/shared_tools/tools/browser/page_inspect/button_summarizer.rb +140 -0
  20. data/lib/shared_tools/tools/browser/page_inspect/form_summarizer.rb +98 -0
  21. data/lib/shared_tools/tools/browser/page_inspect/html_summarizer.rb +37 -0
  22. data/lib/shared_tools/tools/browser/page_inspect/link_summarizer.rb +103 -0
  23. data/lib/shared_tools/tools/browser/page_inspect_tool.rb +55 -0
  24. data/lib/shared_tools/tools/browser/page_screenshot_tool.rb +39 -0
  25. data/lib/shared_tools/tools/browser/selector_generator/base_selectors.rb +28 -0
  26. data/lib/shared_tools/tools/browser/selector_generator/contextual_selectors.rb +140 -0
  27. data/lib/shared_tools/tools/browser/selector_generator.rb +73 -0
  28. data/lib/shared_tools/tools/browser/selector_inspect_tool.rb +67 -0
  29. data/lib/shared_tools/tools/browser/text_field_area_set_tool.rb +45 -0
  30. data/lib/shared_tools/tools/browser/visit_tool.rb +43 -0
  31. data/lib/shared_tools/tools/browser/watir_driver.rb +132 -0
  32. data/lib/shared_tools/tools/browser.rb +27 -0
  33. data/lib/shared_tools/tools/browser_tool.rb +255 -0
  34. data/lib/shared_tools/tools/calculator_tool.rb +169 -0
  35. data/lib/shared_tools/tools/composite_analysis_tool.rb +520 -0
  36. data/lib/shared_tools/tools/computer/base_driver.rb +177 -0
  37. data/lib/shared_tools/tools/computer/mac_driver.rb +103 -0
  38. data/lib/shared_tools/tools/computer.rb +21 -0
  39. data/lib/shared_tools/tools/computer_tool.rb +207 -0
  40. data/lib/shared_tools/tools/data_science_kit.rb +707 -0
  41. data/lib/shared_tools/tools/database/base_driver.rb +17 -0
  42. data/lib/shared_tools/tools/database/postgres_driver.rb +30 -0
  43. data/lib/shared_tools/tools/database/sqlite_driver.rb +29 -0
  44. data/lib/shared_tools/tools/database.rb +9 -0
  45. data/lib/shared_tools/tools/database_query_tool.rb +313 -0
  46. data/lib/shared_tools/tools/database_tool.rb +99 -0
  47. data/lib/shared_tools/tools/devops_toolkit.rb +420 -0
  48. data/lib/shared_tools/tools/disk/base_driver.rb +91 -0
  49. data/lib/shared_tools/tools/disk/base_tool.rb +20 -0
  50. data/lib/shared_tools/tools/disk/directory_create_tool.rb +39 -0
  51. data/lib/shared_tools/tools/disk/directory_delete_tool.rb +39 -0
  52. data/lib/shared_tools/tools/disk/directory_list_tool.rb +37 -0
  53. data/lib/shared_tools/tools/disk/directory_move_tool.rb +40 -0
  54. data/lib/shared_tools/tools/disk/file_create_tool.rb +38 -0
  55. data/lib/shared_tools/tools/disk/file_delete_tool.rb +40 -0
  56. data/lib/shared_tools/tools/disk/file_move_tool.rb +43 -0
  57. data/lib/shared_tools/tools/disk/file_read_tool.rb +40 -0
  58. data/lib/shared_tools/tools/disk/file_replace_tool.rb +44 -0
  59. data/lib/shared_tools/tools/disk/file_write_tool.rb +40 -0
  60. data/lib/shared_tools/tools/disk/local_driver.rb +91 -0
  61. data/lib/shared_tools/tools/disk.rb +17 -0
  62. data/lib/shared_tools/tools/disk_tool.rb +132 -0
  63. data/lib/shared_tools/tools/doc/pdf_reader_tool.rb +79 -0
  64. data/lib/shared_tools/tools/doc.rb +8 -0
  65. data/lib/shared_tools/tools/doc_tool.rb +109 -0
  66. data/lib/shared_tools/tools/docker/base_tool.rb +56 -0
  67. data/lib/shared_tools/tools/docker/compose_run_tool.rb +77 -0
  68. data/lib/shared_tools/tools/docker.rb +8 -0
  69. data/lib/shared_tools/tools/error_handling_tool.rb +403 -0
  70. data/lib/shared_tools/tools/eval/python_eval_tool.rb +209 -0
  71. data/lib/shared_tools/tools/eval/ruby_eval_tool.rb +93 -0
  72. data/lib/shared_tools/tools/eval/shell_eval_tool.rb +64 -0
  73. data/lib/shared_tools/tools/eval.rb +10 -0
  74. data/lib/shared_tools/tools/eval_tool.rb +139 -0
  75. data/lib/shared_tools/tools/secure_tool_template.rb +353 -0
  76. data/lib/shared_tools/tools/version.rb +7 -0
  77. data/lib/shared_tools/tools/weather_tool.rb +197 -0
  78. data/lib/shared_tools/tools/workflow_manager_tool.rb +312 -0
  79. data/lib/shared_tools/tools.rb +16 -0
  80. data/lib/shared_tools/version.rb +1 -1
  81. data/lib/shared_tools.rb +9 -24
  82. metadata +189 -68
  83. data/lib/shared_tools/llm_rb/run_shell_command.rb +0 -23
  84. data/lib/shared_tools/llm_rb.rb +0 -9
  85. data/lib/shared_tools/omniai.rb +0 -9
  86. data/lib/shared_tools/raix/what_is_the_weather.rb +0 -18
  87. data/lib/shared_tools/raix.rb +0 -9
  88. data/lib/shared_tools/ruby_llm/edit_file.rb +0 -71
  89. data/lib/shared_tools/ruby_llm/incomplete/calculator_tool.rb +0 -70
  90. data/lib/shared_tools/ruby_llm/incomplete/composite_analysis_tool.rb +0 -89
  91. data/lib/shared_tools/ruby_llm/incomplete/data_science_kit.rb +0 -128
  92. data/lib/shared_tools/ruby_llm/incomplete/database_query_tool.rb +0 -100
  93. data/lib/shared_tools/ruby_llm/incomplete/devops_toolkit.rb +0 -112
  94. data/lib/shared_tools/ruby_llm/incomplete/error_handling_tool.rb +0 -109
  95. data/lib/shared_tools/ruby_llm/incomplete/secure_tool_template.rb +0 -117
  96. data/lib/shared_tools/ruby_llm/incomplete/weather_tool.rb +0 -110
  97. data/lib/shared_tools/ruby_llm/incomplete/workflow_manager_tool.rb +0 -145
  98. data/lib/shared_tools/ruby_llm/list_files.rb +0 -49
  99. data/lib/shared_tools/ruby_llm/mcp/imcp.rb +0 -15
  100. data/lib/shared_tools/ruby_llm/mcp.rb +0 -12
  101. data/lib/shared_tools/ruby_llm/pdf_page_reader.rb +0 -59
  102. data/lib/shared_tools/ruby_llm/python_eval.rb +0 -194
  103. data/lib/shared_tools/ruby_llm/read_file.rb +0 -40
  104. data/lib/shared_tools/ruby_llm/ruby_eval.rb +0 -77
  105. data/lib/shared_tools/ruby_llm/run_shell_command.rb +0 -49
  106. data/lib/shared_tools/ruby_llm.rb +0 -12
@@ -0,0 +1,312 @@
1
+ # workflow_manager_tool.rb - Managing state across tool invocations
2
+ require 'ruby_llm/tool'
3
+ require 'securerandom'
4
+ require 'json'
5
+ require 'fileutils'
6
+
7
+ module SharedTools
8
+ module Tools
9
+ class WorkflowManagerTool < RubyLLM::Tool
10
+ def self.name = 'workflow_manager'
11
+
12
+ description <<~'DESCRIPTION'
13
+ Manage complex multi-step workflows with persistent state tracking across tool invocations.
14
+ This tool enables the creation and management of stateful workflows that can span multiple
15
+ AI interactions and tool calls. It provides workflow initialization, step-by-step execution,
16
+ status monitoring, and completion tracking. Each workflow maintains its state in persistent
17
+ storage, allowing for resumption of long-running processes and coordination between
18
+ multiple tools and AI interactions. Perfect for complex automation tasks that require
19
+ multiple stages and decision points.
20
+
21
+ Workflow lifecycle:
22
+ 1. Start: Initialize a new workflow with initial data (returns workflow_id)
23
+ 2. Step: Execute multiple workflow steps using the workflow_id
24
+ 3. Status: Check workflow progress and state at any time
25
+ 4. Complete: Finalize the workflow and clean up resources
26
+
27
+ All workflow state is persisted to disk and can survive process restarts.
28
+ DESCRIPTION
29
+
30
+ params do
31
+ string :action, description: <<~DESC.strip
32
+ Workflow management action to perform:
33
+ - 'start': Initialize a new workflow with initial data and return workflow ID
34
+ - 'step': Execute the next step in an existing workflow using provided step data
35
+ - 'status': Check the current status and progress of an existing workflow
36
+ - 'complete': Mark a workflow as finished and clean up associated resources
37
+ Each action requires different combinations of the other parameters.
38
+ DESC
39
+
40
+ string :workflow_id, description: <<~DESC.strip, required: false
41
+ Unique identifier for an existing workflow. Required for 'step', 'status', and 'complete'
42
+ actions. This ID is returned when starting a new workflow and should be used for all
43
+ subsequent operations on that workflow. The ID is a UUID string that ensures
44
+ uniqueness across all workflow instances.
45
+ DESC
46
+
47
+ object :step_data, description: <<~DESC.strip, required: false do
48
+ Data and parameters specific to the current workflow step. The structure is flexible
49
+ and depends on the workflow type and step requirements. Can contain any JSON-serializable
50
+ data types including nested objects and arrays.
51
+ DESC
52
+ # Note: This is a flexible object - actual properties vary by workflow type
53
+ # We define common optional fields but workflow steps can include any data
54
+ string :step_name, description: "Optional name or identifier for this workflow step", required: false
55
+ string :step_type, description: "Optional type or category of this workflow step", required: false
56
+ string :description, description: "Optional description of what this step does", required: false
57
+ object :parameters, description: "Optional nested parameters for this step", required: false do
58
+ # Flexible nested parameters - structure varies by workflow
59
+ end
60
+ object :metadata, description: "Optional metadata for this workflow step", required: false do
61
+ # Flexible metadata - structure varies by workflow
62
+ end
63
+ end
64
+ end
65
+
66
+ def initialize(logger: nil, storage_dir: nil)
67
+ @logger = logger || RubyLLM.logger
68
+ @storage_dir = storage_dir || ".workflows"
69
+ FileUtils.mkdir_p(@storage_dir) unless Dir.exist?(@storage_dir)
70
+ end
71
+
72
+ def execute(action:, workflow_id: nil, **step_data)
73
+ @logger.info("WorkflowManagerTool#execute action=#{action} workflow_id=#{workflow_id}")
74
+
75
+ case action
76
+ when "start"
77
+ start_workflow(step_data)
78
+ when "step"
79
+ return {success: false, error: "workflow_id required for 'step' action"} unless workflow_id
80
+ process_workflow_step(workflow_id, step_data)
81
+ when "status"
82
+ return {success: false, error: "workflow_id required for 'status' action"} unless workflow_id
83
+ get_workflow_status(workflow_id)
84
+ when "complete"
85
+ return {success: false, error: "workflow_id required for 'complete' action"} unless workflow_id
86
+ complete_workflow(workflow_id)
87
+ else
88
+ {success: false, error: "Unknown action: #{action}"}
89
+ end
90
+ rescue => e
91
+ @logger.error("Workflow operation failed: #{e.message}")
92
+ {
93
+ success: false,
94
+ error: "Workflow operation failed: #{e.message}",
95
+ error_type: e.class.name
96
+ }
97
+ end
98
+
99
+ private
100
+
101
+ # Start a new workflow
102
+ def start_workflow(initial_data)
103
+ workflow_id = SecureRandom.uuid
104
+ workflow_state = {
105
+ id: workflow_id,
106
+ status: "active",
107
+ steps: [],
108
+ created_at: Time.now.iso8601,
109
+ updated_at: Time.now.iso8601,
110
+ data: initial_data,
111
+ metadata: {
112
+ step_count: 0,
113
+ last_step_at: nil
114
+ }
115
+ }
116
+
117
+ save_workflow_state(workflow_id, workflow_state)
118
+ @logger.info("Workflow started: #{workflow_id}")
119
+
120
+ {
121
+ success: true,
122
+ workflow_id: workflow_id,
123
+ status: "started",
124
+ created_at: workflow_state[:created_at],
125
+ next_actions: suggested_next_actions(workflow_state)
126
+ }
127
+ end
128
+
129
+ # Process a workflow step
130
+ def process_workflow_step(workflow_id, step_data)
131
+ workflow_state = load_workflow_state(workflow_id)
132
+ return {success: false, error: "Workflow not found: #{workflow_id}"} unless workflow_state
133
+
134
+ if workflow_state[:status] == "completed"
135
+ return {success: false, error: "Cannot add steps to completed workflow"}
136
+ end
137
+
138
+ step_number = workflow_state[:steps].length + 1
139
+ step_result = process_step_logic(step_data, workflow_state)
140
+
141
+ step = {
142
+ step_number: step_number,
143
+ data: step_data,
144
+ result: step_result,
145
+ processed_at: Time.now.iso8601,
146
+ execution_time_seconds: 0.001 # Placeholder
147
+ }
148
+
149
+ workflow_state[:steps] << step
150
+ workflow_state[:updated_at] = Time.now.iso8601
151
+ workflow_state[:metadata][:step_count] = step_number
152
+ workflow_state[:metadata][:last_step_at] = step[:processed_at]
153
+
154
+ save_workflow_state(workflow_id, workflow_state)
155
+ @logger.info("Workflow step #{step_number} completed: #{workflow_id}")
156
+
157
+ {
158
+ success: true,
159
+ workflow_id: workflow_id,
160
+ step_number: step_number,
161
+ step_result: step_result,
162
+ workflow_status: workflow_state[:status],
163
+ total_steps: step_number,
164
+ next_actions: suggested_next_actions(workflow_state)
165
+ }
166
+ end
167
+
168
+ # Get workflow status
169
+ def get_workflow_status(workflow_id)
170
+ workflow_state = load_workflow_state(workflow_id)
171
+ return {success: false, error: "Workflow not found: #{workflow_id}"} unless workflow_state
172
+
173
+ @logger.debug("Workflow status retrieved: #{workflow_id}")
174
+
175
+ {
176
+ success: true,
177
+ workflow_id: workflow_id,
178
+ status: workflow_state[:status],
179
+ created_at: workflow_state[:created_at],
180
+ updated_at: workflow_state[:updated_at],
181
+ step_count: workflow_state[:steps].length,
182
+ steps: workflow_state[:steps].map { |step|
183
+ {
184
+ step_number: step[:step_number],
185
+ processed_at: step[:processed_at],
186
+ has_result: !step[:result].nil?
187
+ }
188
+ },
189
+ metadata: workflow_state[:metadata],
190
+ next_actions: suggested_next_actions(workflow_state)
191
+ }
192
+ end
193
+
194
+ # Complete a workflow
195
+ def complete_workflow(workflow_id)
196
+ workflow_state = load_workflow_state(workflow_id)
197
+ return {success: false, error: "Workflow not found: #{workflow_id}"} unless workflow_state
198
+
199
+ if workflow_state[:status] == "completed"
200
+ return {success: false, error: "Workflow already completed"}
201
+ end
202
+
203
+ workflow_state[:status] = "completed"
204
+ workflow_state[:completed_at] = Time.now.iso8601
205
+ workflow_state[:updated_at] = Time.now.iso8601
206
+
207
+ save_workflow_state(workflow_id, workflow_state)
208
+ @logger.info("Workflow completed: #{workflow_id}")
209
+
210
+ {
211
+ success: true,
212
+ workflow_id: workflow_id,
213
+ status: "completed",
214
+ completed_at: workflow_state[:completed_at],
215
+ total_steps: workflow_state[:steps].length,
216
+ summary: {
217
+ created_at: workflow_state[:created_at],
218
+ completed_at: workflow_state[:completed_at],
219
+ total_steps: workflow_state[:steps].length,
220
+ duration_seconds: calculate_duration(workflow_state)
221
+ }
222
+ }
223
+ end
224
+
225
+ # Calculate workflow duration
226
+ def calculate_duration(workflow_state)
227
+ return 0 unless workflow_state[:completed_at] && workflow_state[:created_at]
228
+
229
+ completed = Time.parse(workflow_state[:completed_at])
230
+ created = Time.parse(workflow_state[:created_at])
231
+ (completed - created).round(2)
232
+ end
233
+
234
+ # Suggest next actions based on workflow state
235
+ def suggested_next_actions(workflow_state)
236
+ return [] if workflow_state[:status] == "completed"
237
+
238
+ actions = []
239
+
240
+ # Always suggest adding a step
241
+ actions << {
242
+ action: "step",
243
+ description: "Add the next workflow step",
244
+ required_params: ["workflow_id", "step_data"]
245
+ }
246
+
247
+ # Suggest status check
248
+ actions << {
249
+ action: "status",
250
+ description: "Check workflow progress and status",
251
+ required_params: ["workflow_id"]
252
+ }
253
+
254
+ # Suggest completion if workflow has steps
255
+ if workflow_state[:steps].length > 0
256
+ actions << {
257
+ action: "complete",
258
+ description: "Mark workflow as complete",
259
+ required_params: ["workflow_id"]
260
+ }
261
+ end
262
+
263
+ actions
264
+ end
265
+
266
+ # Process individual step logic
267
+ def process_step_logic(step_data, workflow_state)
268
+ # This is a demonstration - in production, this would contain
269
+ # actual business logic for processing workflow steps
270
+
271
+ {
272
+ processed: true,
273
+ input_keys: step_data.keys,
274
+ workflow_context: {
275
+ current_step: workflow_state[:steps].length + 1,
276
+ total_steps_so_far: workflow_state[:steps].length
277
+ },
278
+ timestamp: Time.now.iso8601
279
+ }
280
+ end
281
+
282
+ # Save workflow state to disk
283
+ def save_workflow_state(workflow_id, state)
284
+ file_path = workflow_file_path(workflow_id)
285
+ File.write(file_path, JSON.pretty_generate(state))
286
+ @logger.debug("Workflow state saved: #{file_path}")
287
+ rescue => e
288
+ @logger.error("Failed to save workflow state: #{e.message}")
289
+ raise
290
+ end
291
+
292
+ # Load workflow state from disk
293
+ def load_workflow_state(workflow_id)
294
+ file_path = workflow_file_path(workflow_id)
295
+ return nil unless File.exist?(file_path)
296
+
297
+ JSON.parse(File.read(file_path), symbolize_names: true)
298
+ rescue => e
299
+ @logger.error("Failed to load workflow state: #{e.message}")
300
+ nil
301
+ end
302
+
303
+ # Get workflow file path
304
+ def workflow_file_path(workflow_id)
305
+ File.join(@storage_dir, "workflow_#{workflow_id}.json")
306
+ end
307
+
308
+ # Get storage directory (for testing)
309
+ attr_reader :storage_dir
310
+ end
311
+ end
312
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Loader file for all tools - loaded automatically by Zeitwerk
4
+ # Individual tool collections can be loaded with:
5
+ # require 'shared_tools/tools/disk' # Load all disk tools
6
+ # require 'shared_tools/tools/browser' # Load all browser tools
7
+ #
8
+ # Or load individual tools with Zeitwerk autoloading:
9
+ # SharedTools::Tools::Disk::FileReadTool
10
+ # SharedTools::Tools::Browser::VisitTool
11
+
12
+ module SharedTools
13
+ module Tools
14
+ class Error < StandardError; end
15
+ end
16
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SharedTools
4
- VERSION = "0.2.3"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/shared_tools.rb CHANGED
@@ -1,19 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'ruby_llm'
3
4
  require 'io/console'
4
5
 
5
6
  require "zeitwerk"
6
7
  loader = Zeitwerk::Loader.for_gem
7
8
  # Ignore aggregate loader files that don't define constants
8
9
  loader.ignore("#{__dir__}/shared_tools/ruby_llm.rb")
9
- loader.ignore("#{__dir__}/shared_tools/llm_rb.rb")
10
- loader.ignore("#{__dir__}/shared_tools/omniai.rb")
11
- loader.ignore("#{__dir__}/shared_tools/raix.rb")
10
+ loader.ignore("#{__dir__}/shared_tools/tools/browser.rb")
11
+ loader.ignore("#{__dir__}/shared_tools/tools/computer.rb")
12
+ loader.ignore("#{__dir__}/shared_tools/tools/database.rb")
13
+ loader.ignore("#{__dir__}/shared_tools/tools/disk.rb")
14
+ loader.ignore("#{__dir__}/shared_tools/tools/doc.rb")
15
+ loader.ignore("#{__dir__}/shared_tools/tools/docker.rb")
16
+ loader.ignore("#{__dir__}/shared_tools/tools/eval.rb")
12
17
  loader.setup
13
18
 
14
19
  module SharedTools
15
- SUPPORTED_GEMS ||= %i(ruby_llm llm_rb omniai raix)
16
- @auto_execute ||= false # Human in the loop
20
+ @auto_execute ||= true # Auto-execute by default, no human-in-the-loop
17
21
  class << self
18
22
 
19
23
  def auto_execute(wildwest=true)
@@ -33,24 +37,5 @@ module SharedTools
33
37
  print "\nIs it okay to proceed? (y/N"
34
38
  STDIN.getch == "y"
35
39
  end
36
-
37
-
38
- def detected_gem
39
- return :ruby_llm if defined?(::RubyLLM::Tool)
40
- return :llm_rb if defined?(::LLM) || defined?(::Llm)
41
- return :omniai if defined?(::OmniAI) || defined?(::Omniai)
42
- return :raix if defined?(::Raix::FunctionDispatch)
43
- nil
44
- end
45
-
46
- def verify_gem(a_symbol)
47
- loaded = a_symbol == detected_gem
48
- return true if loaded
49
- raise "SharedTools: Please require '#{a_symbol}' gem before requiring 'shared_tools'."
50
- end
51
- end
52
-
53
- if detected_gem.nil?
54
- warn "⚠️ SharedTools: No supported gem detected. Supported gems are: #{SUPPORTED_GEMS.join(', ')}"
55
40
  end
56
41
  end