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,420 @@
1
+ # devops_toolkit.rb - System administration and deployment tools
2
+ require 'ruby_llm/tool'
3
+ require 'securerandom'
4
+
5
+ module SharedTools
6
+ module Tools
7
+ class DevopsToolkit < RubyLLM::Tool
8
+ def self.name = "devops_toolkit"
9
+
10
+ description <<~'DESCRIPTION'
11
+ Comprehensive DevOps and system administration toolkit for managing application deployments,
12
+ monitoring system health, and performing operational tasks across different environments.
13
+ This tool provides secure, audited access to common DevOps operations including deployments,
14
+ rollbacks, health checks, log analysis, and metrics collection. It includes built-in safety
15
+ mechanisms for production environments, comprehensive logging for compliance, and support
16
+ for multiple deployment environments. All operations are logged and require appropriate
17
+ permissions and confirmations for sensitive environments.
18
+
19
+ Safety features:
20
+ - Production operations require explicit confirmation
21
+ - All operations are logged with unique operation IDs
22
+ - Environment-specific restrictions and validations
23
+ - Rollback capabilities for failed deployments
24
+ - Health check integration before critical operations
25
+
26
+ Supported environments: development, staging, production
27
+ DESCRIPTION
28
+
29
+ params do
30
+ string :operation, description: <<~DESC.strip
31
+ Specific DevOps operation to perform:
32
+ - 'deploy': Deploy application code to the specified environment
33
+ - 'rollback': Revert to the previous stable deployment version
34
+ - 'health_check': Perform comprehensive health and status checks
35
+ - 'log_analysis': Analyze application and system logs for issues
36
+ - 'metric_collection': Gather and report system and application metrics
37
+ Each operation has specific requirements and safety checks.
38
+ DESC
39
+
40
+ string :environment, description: <<~DESC.strip, required: false
41
+ Target environment for the DevOps operation:
42
+ - 'development': Local or shared development environment (minimal restrictions)
43
+ - 'staging': Pre-production environment for testing (moderate restrictions)
44
+ - 'production': Live production environment (maximum restrictions and confirmations)
45
+ Production operations require explicit confirmation via the 'production_confirmed' option.
46
+ Default: staging
47
+ DESC
48
+
49
+ object :options, description: <<~DESC.strip, required: false do
50
+ Operation-specific options and parameters. Different operations use different option combinations.
51
+ Production operations always require 'production_confirmed: true' for safety.
52
+ DESC
53
+ # Production safety
54
+ boolean :production_confirmed, description: "Explicit confirmation for production operations. Must be true for production environment. Default: false", required: false
55
+
56
+ # Deploy operation options
57
+ string :version, description: "Version identifier to deploy. Default: 'latest'", required: false
58
+ string :branch, description: "Git branch to deploy from. Default: 'main'", required: false
59
+ boolean :rollback_on_failure, description: "Automatically rollback if deployment fails. Default: true", required: false
60
+ array :notification_channels, of: :string, description: "Array of notification channels for deployment status. Default: []", required: false
61
+
62
+ # Rollback operation options
63
+ string :target_version, description: "Specific version to rollback to. Default: 'previous'", required: false
64
+ boolean :rollback_confirmed, description: "Extra confirmation for production rollback. Default: false", required: false
65
+
66
+ # Health check options
67
+ array :services_to_check, of: :string, description: "Array of service names to check. Default: ['web', 'api', 'database', 'cache']", required: false
68
+ integer :timeout_seconds, description: "Timeout for health check operations. Default: 30", required: false
69
+
70
+ # Log analysis options
71
+ string :time_range, description: "Time range for log analysis: 'last_hour', 'last_day', 'last_week'. Default: 'last_hour'", required: false
72
+ string :log_level, description: "Minimum log level: 'debug', 'info', 'warning', 'error'. Default: 'error'", required: false
73
+ array :search_patterns, of: :string, description: "Array of regex patterns to search for in logs. Default: []", required: false
74
+
75
+ # Metric collection options
76
+ array :metric_types, of: :string, description: "Array of metric types: 'cpu', 'memory', 'disk', 'network'. Default: all", required: false
77
+ string :time_window, description: "Time window for metrics: 'last_5_minutes', 'last_hour', 'last_day'. Default: 'last_5_minutes'", required: false
78
+ string :output_format, description: "Format for metric output: 'summary', 'detailed', 'json'. Default: 'summary'", required: false
79
+ end
80
+ end
81
+
82
+ def initialize(logger: nil)
83
+ @logger = logger || RubyLLM.logger
84
+ @operation_log = []
85
+ end
86
+
87
+ def execute(operation:, environment: "staging", **options)
88
+ operation_id = SecureRandom.uuid
89
+ @logger.info("DevOpsToolkit#execute operation=#{operation} environment=#{environment} operation_id=#{operation_id}")
90
+
91
+ # Validate environment
92
+ unless valid_environment?(environment)
93
+ return {
94
+ success: false,
95
+ error: "Invalid environment: #{environment}",
96
+ valid_environments: ["development", "staging", "production"]
97
+ }
98
+ end
99
+
100
+ # Security: Require explicit production confirmation
101
+ if environment == "production" && !options[:production_confirmed]
102
+ @logger.warn("Production operation attempted without confirmation")
103
+ return {
104
+ success: false,
105
+ error: "Production operations require explicit confirmation",
106
+ required_option: "production_confirmed: true",
107
+ environment: environment
108
+ }
109
+ end
110
+
111
+ # Log operation
112
+ log_operation(operation_id, operation, environment, options)
113
+
114
+ # Execute operation
115
+ result = case operation
116
+ when "deploy"
117
+ perform_deployment(environment, options, operation_id)
118
+ when "rollback"
119
+ perform_rollback(environment, options, operation_id)
120
+ when "health_check"
121
+ perform_health_check(environment, options, operation_id)
122
+ when "log_analysis"
123
+ analyze_logs(environment, options, operation_id)
124
+ when "metric_collection"
125
+ collect_metrics(environment, options, operation_id)
126
+ else
127
+ {
128
+ success: false,
129
+ error: "Unknown operation: #{operation}",
130
+ valid_operations: ["deploy", "rollback", "health_check", "log_analysis", "metric_collection"]
131
+ }
132
+ end
133
+
134
+ # Add operation_id to result
135
+ result[:operation_id] = operation_id
136
+ result
137
+ rescue => e
138
+ @logger.error("DevOps operation failed: #{e.message}")
139
+ {
140
+ success: false,
141
+ error: "DevOps operation failed: #{e.message}",
142
+ error_type: e.class.name,
143
+ operation_id: operation_id
144
+ }
145
+ end
146
+
147
+ private
148
+
149
+ # Validate environment
150
+ def valid_environment?(environment)
151
+ ["development", "staging", "production"].include?(environment)
152
+ end
153
+
154
+ # Log operation for audit trail
155
+ def log_operation(operation_id, operation, environment, options)
156
+ log_entry = {
157
+ operation_id: operation_id,
158
+ operation: operation,
159
+ environment: environment,
160
+ options_summary: options.keys,
161
+ timestamp: Time.now.iso8601,
162
+ user: "system" # In production: actual user from auth context
163
+ }
164
+
165
+ @operation_log << log_entry
166
+ @logger.info("DevOps operation logged: #{operation_id}")
167
+ end
168
+
169
+ # Perform deployment
170
+ def perform_deployment(environment, options, operation_id)
171
+ @logger.info("Starting deployment to #{environment}")
172
+
173
+ version = options[:version] || "latest"
174
+ branch = options[:branch] || "main"
175
+ # rollback_on_failure = options[:rollback_on_failure].nil? ? true : options[:rollback_on_failure]
176
+
177
+ # Simulate deployment steps
178
+ deployment_steps = [
179
+ {step: "pre_deployment_checks", status: "completed", duration: 0.5},
180
+ {step: "backup_current_version", status: "completed", duration: 1.0},
181
+ {step: "deploy_new_version", status: "completed", duration: 2.5},
182
+ {step: "run_migrations", status: "completed", duration: 1.5},
183
+ {step: "post_deployment_checks", status: "completed", duration: 1.0}
184
+ ]
185
+
186
+ @logger.info("Deployment completed successfully")
187
+
188
+ {
189
+ success: true,
190
+ operation: "deploy",
191
+ environment: environment,
192
+ deployment_id: SecureRandom.uuid,
193
+ version: version,
194
+ branch: branch,
195
+ deployed_at: Time.now.iso8601,
196
+ deployment_steps: deployment_steps,
197
+ rollback_available: true,
198
+ total_duration_seconds: deployment_steps.sum { |s| s[:duration] },
199
+ details: "Deployment completed successfully to #{environment}"
200
+ }
201
+ end
202
+
203
+ # Perform rollback
204
+ def perform_rollback(environment, options, operation_id)
205
+ @logger.info("Starting rollback in #{environment}")
206
+
207
+ target_version = options[:target_version] || "previous"
208
+
209
+ # Production rollbacks need extra confirmation
210
+ if environment == "production" && !options[:rollback_confirmed]
211
+ @logger.warn("Production rollback requires confirmation")
212
+ return {
213
+ success: false,
214
+ error: "Production rollback requires explicit confirmation",
215
+ required_option: "rollback_confirmed: true",
216
+ environment: environment
217
+ }
218
+ end
219
+
220
+ rollback_steps = [
221
+ {step: "validate_target_version", status: "completed", duration: 0.5},
222
+ {step: "stop_current_services", status: "completed", duration: 1.0},
223
+ {step: "restore_previous_version", status: "completed", duration: 2.0},
224
+ {step: "restart_services", status: "completed", duration: 1.5},
225
+ {step: "verify_rollback", status: "completed", duration: 1.0}
226
+ ]
227
+
228
+ @logger.info("Rollback completed successfully")
229
+
230
+ {
231
+ success: true,
232
+ operation: "rollback",
233
+ environment: environment,
234
+ rollback_id: SecureRandom.uuid,
235
+ target_version: target_version,
236
+ rolled_back_at: Time.now.iso8601,
237
+ rollback_steps: rollback_steps,
238
+ total_duration_seconds: rollback_steps.sum { |s| s[:duration] },
239
+ details: "Successfully rolled back to #{target_version}"
240
+ }
241
+ end
242
+
243
+ # Perform health check
244
+ def perform_health_check(environment, options, operation_id)
245
+ @logger.info("Performing health check for #{environment}")
246
+
247
+ services_to_check = options[:services_to_check] || ["web", "api", "database", "cache"]
248
+ timeout_seconds = options[:timeout_seconds] || 30
249
+
250
+ # Simulate health checks
251
+ health_results = services_to_check.map do |service|
252
+ {
253
+ service: service,
254
+ status: "healthy",
255
+ response_time_ms: rand(50..200),
256
+ last_check: Time.now.iso8601,
257
+ details: "Service operational"
258
+ }
259
+ end
260
+
261
+ all_healthy = health_results.all? { |r| r[:status] == "healthy" }
262
+
263
+ @logger.info("Health check completed: #{all_healthy ? 'All services healthy' : 'Issues detected'}")
264
+
265
+ {
266
+ success: true,
267
+ operation: "health_check",
268
+ environment: environment,
269
+ overall_status: all_healthy ? "healthy" : "degraded",
270
+ services_checked: services_to_check.length,
271
+ healthy_services: health_results.count { |r| r[:status] == "healthy" },
272
+ health_results: health_results,
273
+ checked_at: Time.now.iso8601,
274
+ check_duration_seconds: timeout_seconds
275
+ }
276
+ end
277
+
278
+ # Analyze logs
279
+ def analyze_logs(environment, options, operation_id)
280
+ @logger.info("Analyzing logs for #{environment}")
281
+
282
+ time_range = options[:time_range] || "last_hour"
283
+ log_level = options[:log_level] || "error"
284
+ # search_patterns = options[:search_patterns] || []
285
+
286
+ # Simulate log analysis
287
+ log_entries_analyzed = 5000
288
+ errors_found = rand(0..20)
289
+ warnings_found = rand(5..50)
290
+
291
+ findings = []
292
+
293
+ if errors_found > 0
294
+ findings << {
295
+ severity: "error",
296
+ count: errors_found,
297
+ pattern: "Exception in /api/users",
298
+ first_occurrence: (Time.now - 3600).iso8601,
299
+ last_occurrence: Time.now.iso8601
300
+ }
301
+ end
302
+
303
+ if warnings_found > 10
304
+ findings << {
305
+ severity: "warning",
306
+ count: warnings_found,
307
+ pattern: "Slow query detected",
308
+ first_occurrence: (Time.now - 1800).iso8601,
309
+ last_occurrence: Time.now.iso8601
310
+ }
311
+ end
312
+
313
+ @logger.info("Log analysis completed: #{findings.length} issues found")
314
+
315
+ {
316
+ success: true,
317
+ operation: "log_analysis",
318
+ environment: environment,
319
+ time_range: time_range,
320
+ log_level: log_level,
321
+ entries_analyzed: log_entries_analyzed,
322
+ errors_found: errors_found,
323
+ warnings_found: warnings_found,
324
+ findings: findings,
325
+ analyzed_at: Time.now.iso8601,
326
+ recommendations: generate_log_recommendations(findings)
327
+ }
328
+ end
329
+
330
+ # Collect metrics
331
+ def collect_metrics(environment, options, operation_id)
332
+ @logger.info("Collecting metrics for #{environment}")
333
+
334
+ metric_types = options[:metric_types] || ["cpu", "memory", "disk", "network"]
335
+ time_window = options[:time_window] || "last_5_minutes"
336
+ output_format = options[:output_format] || "summary"
337
+
338
+ # Simulate metric collection
339
+ metrics = metric_types.map do |metric_type|
340
+ case metric_type
341
+ when "cpu"
342
+ {
343
+ type: "cpu",
344
+ current_usage_percent: rand(10..90),
345
+ average_usage_percent: rand(20..70),
346
+ peak_usage_percent: rand(50..100),
347
+ unit: "percent"
348
+ }
349
+ when "memory"
350
+ {
351
+ type: "memory",
352
+ current_usage_gb: rand(1.0..8.0).round(2),
353
+ total_gb: 16.0,
354
+ usage_percent: rand(20..80),
355
+ unit: "gigabytes"
356
+ }
357
+ when "disk"
358
+ {
359
+ type: "disk",
360
+ current_usage_gb: rand(10.0..100.0).round(2),
361
+ total_gb: 500.0,
362
+ usage_percent: rand(10..60),
363
+ unit: "gigabytes"
364
+ }
365
+ when "network"
366
+ {
367
+ type: "network",
368
+ ingress_mbps: rand(1.0..50.0).round(2),
369
+ egress_mbps: rand(1.0..50.0).round(2),
370
+ unit: "megabits_per_second"
371
+ }
372
+ else
373
+ {
374
+ type: metric_type,
375
+ status: "unknown",
376
+ message: "Metric type not implemented"
377
+ }
378
+ end
379
+ end
380
+
381
+ @logger.info("Metrics collection completed: #{metrics.length} metrics")
382
+
383
+ {
384
+ success: true,
385
+ operation: "metric_collection",
386
+ environment: environment,
387
+ time_window: time_window,
388
+ metrics_collected: metrics.length,
389
+ metrics: metrics,
390
+ collected_at: Time.now.iso8601,
391
+ output_format: output_format
392
+ }
393
+ end
394
+
395
+ # Generate recommendations based on log findings
396
+ def generate_log_recommendations(findings)
397
+ recommendations = []
398
+
399
+ findings.each do |finding|
400
+ case finding[:severity]
401
+ when "error"
402
+ recommendations << "Investigate #{finding[:pattern]} - #{finding[:count]} occurrences"
403
+ when "warning"
404
+ if finding[:count] > 20
405
+ recommendations << "High frequency of #{finding[:pattern]} - consider optimization"
406
+ end
407
+ end
408
+ end
409
+
410
+ recommendations << "No critical issues found" if recommendations.empty?
411
+ recommendations
412
+ end
413
+
414
+ # Accessor for operation log (for testing)
415
+ def operation_log
416
+ @operation_log
417
+ end
418
+ end
419
+ end
420
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module SharedTools
6
+ module Tools
7
+ module Disk
8
+ # A driver for interacting with a disk via various operations
9
+ class BaseDriver
10
+ # @param root [String]
11
+ def initialize(root:)
12
+ @root = Pathname(root)
13
+ end
14
+
15
+ # @param path [String]
16
+ def directory_create(path:)
17
+ raise NotImplementedError, "#{self.class}#{__method__} undefined"
18
+ end
19
+
20
+ # @param path [String]
21
+ def directroy_delete(path:)
22
+ raise NotImplementedError, "#{self.class}#{__method__} undefined"
23
+ end
24
+
25
+ # @param path [String]
26
+ # @param destination [String]
27
+ def directory_move(path:, destination:)
28
+ raise NotImplementedError, "#{self.class}#{__method__} undefined"
29
+ end
30
+
31
+ # @param path [String]
32
+ def file_create(path:)
33
+ raise NotImplementedError, "#{self.class}#{__method__} undefined"
34
+ end
35
+
36
+ # @param path [String]
37
+ def file_delete(path:)
38
+ raise NotImplementedError, "#{self.class}#{__method__} undefined"
39
+ end
40
+
41
+ # @param path [String]
42
+ # @param destination [String]
43
+ def file_move(path:, destination:)
44
+ raise NotImplementedError, "#{self.class}#{__method__} undefined"
45
+ end
46
+
47
+ # @param path [String]
48
+ # @return [String]
49
+ def file_read(path:)
50
+ raise NotImplementedError, "#{self.class}#{__method__} undefined"
51
+ end
52
+
53
+ # @param old_text [String]
54
+ # @param new_text [String]
55
+ # @param path [String]
56
+ def file_replace(old_text:, new_text:, path:)
57
+ raise NotImplementedError, "#{self.class}#{__method__} undefined"
58
+ end
59
+
60
+ # @param path [String]
61
+ # @param text [String]
62
+ def file_write(path:, text:)
63
+ raise NotImplementedError, "#{self.class}#{__method__} undefined"
64
+ end
65
+
66
+ protected
67
+
68
+ # @param path [String]
69
+ #
70
+ # @raise [SecurityError]
71
+ #
72
+ # @return Pathname
73
+ def resolve!(path:)
74
+ @root.join(path).tap do |resolved|
75
+ relative = resolved.ascend.any? { |ancestor| ancestor.eql?(@root) }
76
+ raise SecurityError, "unknown path=#{resolved}" unless relative
77
+ end
78
+ end
79
+
80
+ # @param path [String]
81
+ def summarize(path:)
82
+ if File.directory?(path)
83
+ "📁 ./#{path}/"
84
+ else
85
+ "📄 ./#{path} (#{File.size(path)} bytes)"
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SharedTools
4
+ module Tools
5
+ module Disk
6
+ # @example
7
+ # class ExampleTool < SharedTools::Tools::Disk::BaseTool
8
+ # description "..."
9
+ # end
10
+ class BaseTool
11
+ # @param driver [SharedTools::Tools::Disk::BaseDriver] A driver for interacting with the disk.
12
+ # @param logger [IO] An optional logger for debugging executed commands.
13
+ def initialize(driver:, logger: Logger.new(IO::NULL))
14
+ @driver = driver
15
+ @logger = logger
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,39 @@
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::DirectoryCreateTool.new(root: "./project")
10
+ # tool.execute(path: "./foo/bar")
11
+ class DirectoryCreateTool < ::RubyLLM::Tool
12
+ def self.name = 'disk_directory_create'
13
+
14
+ description "Creates a directory."
15
+
16
+ params do
17
+ string :path, description: "a path to the directory (e.g. `./foo/bar`)"
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
+
31
+ @driver.directory_create(path:)
32
+ rescue SecurityError => e
33
+ @logger.error(e.message)
34
+ raise e
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,39 @@
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::DirectoryDeleteTool.new(root: "./project")
10
+ # tool.execute(path: "./foo/bar")
11
+ class DirectoryDeleteTool < ::RubyLLM::Tool
12
+ def self.name = 'disk_directory_delete'
13
+
14
+ description "Deletes a directory."
15
+
16
+ params do
17
+ string :path, description: "a path to the directory (e.g. `./foo/bar`)"
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
+
31
+ @driver.directory_delete(path:)
32
+ rescue SecurityError => e
33
+ @logger.error(e.message)
34
+ raise e
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,37 @@
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::SummaryTool.new(root: "./project")
10
+ # tool.execute
11
+ class DirectoryListTool < ::RubyLLM::Tool
12
+ def self.name = 'disk_directory_list'
13
+
14
+ description "Summarizes the contents (files and directories) of a directory."
15
+
16
+ params do
17
+ string :path, description: "a path to the directory (e.g. `./foo/bar`)", required: false
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
+ # @return [String]
26
+ def execute(path: ".")
27
+ @logger.info("#{self.class.name}#execute")
28
+
29
+ @driver.directory_list(path:)
30
+ rescue SecurityError => e
31
+ @logger.error(e.message)
32
+ raise e
33
+ end
34
+ end
35
+ end
36
+ end
37
+ 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::DirectoryMoveTool.new(root: "./project")
10
+ # tool.execute(path: "./foo", destination: "./bar")
11
+ class DirectoryMoveTool < ::RubyLLM::Tool
12
+ def self.name = 'disk_directory_move'
13
+
14
+ description "Moves a directory from one location to another."
15
+
16
+ params do
17
+ string :path, description: "a path (e.g. `./old`)"
18
+ string :destination, description: "a path (e.g. `./new`)"
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 destination [String]
28
+ #
29
+ # @return [String]
30
+ def execute(path:, destination:)
31
+ @logger.info("#{self.class.name}#execute path=#{path.inspect} destination=#{destination.inspect}")
32
+ @driver.directory_move(path:, destination:)
33
+ rescue SecurityError => e
34
+ @logger.error(e.message)
35
+ raise e
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end