shared_tools 0.2.1 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/README.md +594 -42
- data/lib/shared_tools/{ruby_llm/mcp → mcp}/github_mcp_server.rb +31 -24
- data/lib/shared_tools/mcp/imcp.rb +28 -0
- data/lib/shared_tools/mcp/tavily_mcp_server.rb +44 -0
- data/lib/shared_tools/mcp.rb +24 -0
- data/lib/shared_tools/tools/browser/base_driver.rb +64 -0
- data/lib/shared_tools/tools/browser/base_tool.rb +50 -0
- data/lib/shared_tools/tools/browser/click_tool.rb +54 -0
- data/lib/shared_tools/tools/browser/elements/element_grouper.rb +73 -0
- data/lib/shared_tools/tools/browser/elements/nearby_element_detector.rb +109 -0
- data/lib/shared_tools/tools/browser/formatters/action_formatter.rb +37 -0
- data/lib/shared_tools/tools/browser/formatters/data_entry_formatter.rb +135 -0
- data/lib/shared_tools/tools/browser/formatters/element_formatter.rb +52 -0
- data/lib/shared_tools/tools/browser/formatters/input_formatter.rb +59 -0
- data/lib/shared_tools/tools/browser/inspect_tool.rb +87 -0
- data/lib/shared_tools/tools/browser/inspect_utils.rb +51 -0
- data/lib/shared_tools/tools/browser/page_inspect/button_summarizer.rb +140 -0
- data/lib/shared_tools/tools/browser/page_inspect/form_summarizer.rb +98 -0
- data/lib/shared_tools/tools/browser/page_inspect/html_summarizer.rb +37 -0
- data/lib/shared_tools/tools/browser/page_inspect/link_summarizer.rb +103 -0
- data/lib/shared_tools/tools/browser/page_inspect_tool.rb +55 -0
- data/lib/shared_tools/tools/browser/page_screenshot_tool.rb +39 -0
- data/lib/shared_tools/tools/browser/selector_generator/base_selectors.rb +28 -0
- data/lib/shared_tools/tools/browser/selector_generator/contextual_selectors.rb +140 -0
- data/lib/shared_tools/tools/browser/selector_generator.rb +73 -0
- data/lib/shared_tools/tools/browser/selector_inspect_tool.rb +67 -0
- data/lib/shared_tools/tools/browser/text_field_area_set_tool.rb +45 -0
- data/lib/shared_tools/tools/browser/visit_tool.rb +43 -0
- data/lib/shared_tools/tools/browser/watir_driver.rb +132 -0
- data/lib/shared_tools/tools/browser.rb +27 -0
- data/lib/shared_tools/tools/browser_tool.rb +255 -0
- data/lib/shared_tools/tools/calculator_tool.rb +169 -0
- data/lib/shared_tools/tools/composite_analysis_tool.rb +520 -0
- data/lib/shared_tools/tools/computer/base_driver.rb +177 -0
- data/lib/shared_tools/tools/computer/mac_driver.rb +103 -0
- data/lib/shared_tools/tools/computer.rb +21 -0
- data/lib/shared_tools/tools/computer_tool.rb +207 -0
- data/lib/shared_tools/tools/data_science_kit.rb +707 -0
- data/lib/shared_tools/tools/database/base_driver.rb +17 -0
- data/lib/shared_tools/tools/database/postgres_driver.rb +30 -0
- data/lib/shared_tools/tools/database/sqlite_driver.rb +29 -0
- data/lib/shared_tools/tools/database.rb +9 -0
- data/lib/shared_tools/tools/database_query_tool.rb +313 -0
- data/lib/shared_tools/tools/database_tool.rb +99 -0
- data/lib/shared_tools/tools/devops_toolkit.rb +420 -0
- data/lib/shared_tools/tools/disk/base_driver.rb +91 -0
- data/lib/shared_tools/tools/disk/base_tool.rb +20 -0
- data/lib/shared_tools/tools/disk/directory_create_tool.rb +39 -0
- data/lib/shared_tools/tools/disk/directory_delete_tool.rb +39 -0
- data/lib/shared_tools/tools/disk/directory_list_tool.rb +37 -0
- data/lib/shared_tools/tools/disk/directory_move_tool.rb +40 -0
- data/lib/shared_tools/tools/disk/file_create_tool.rb +38 -0
- data/lib/shared_tools/tools/disk/file_delete_tool.rb +40 -0
- data/lib/shared_tools/tools/disk/file_move_tool.rb +43 -0
- data/lib/shared_tools/tools/disk/file_read_tool.rb +40 -0
- data/lib/shared_tools/tools/disk/file_replace_tool.rb +44 -0
- data/lib/shared_tools/tools/disk/file_write_tool.rb +40 -0
- data/lib/shared_tools/tools/disk/local_driver.rb +91 -0
- data/lib/shared_tools/tools/disk.rb +17 -0
- data/lib/shared_tools/tools/disk_tool.rb +132 -0
- data/lib/shared_tools/tools/doc/pdf_reader_tool.rb +79 -0
- data/lib/shared_tools/tools/doc.rb +8 -0
- data/lib/shared_tools/tools/doc_tool.rb +109 -0
- data/lib/shared_tools/tools/docker/base_tool.rb +56 -0
- data/lib/shared_tools/tools/docker/compose_run_tool.rb +77 -0
- data/lib/shared_tools/tools/docker.rb +8 -0
- data/lib/shared_tools/tools/error_handling_tool.rb +403 -0
- data/lib/shared_tools/tools/eval/python_eval_tool.rb +209 -0
- data/lib/shared_tools/tools/eval/ruby_eval_tool.rb +93 -0
- data/lib/shared_tools/tools/eval/shell_eval_tool.rb +64 -0
- data/lib/shared_tools/tools/eval.rb +10 -0
- data/lib/shared_tools/tools/eval_tool.rb +139 -0
- data/lib/shared_tools/tools/secure_tool_template.rb +353 -0
- data/lib/shared_tools/tools/version.rb +7 -0
- data/lib/shared_tools/tools/weather_tool.rb +197 -0
- data/lib/shared_tools/tools/workflow_manager_tool.rb +312 -0
- data/lib/shared_tools/tools.rb +16 -0
- data/lib/shared_tools/version.rb +1 -1
- data/lib/shared_tools.rb +9 -33
- metadata +189 -68
- data/lib/shared_tools/llm_rb/run_shell_command.rb +0 -23
- data/lib/shared_tools/llm_rb.rb +0 -9
- data/lib/shared_tools/omniai.rb +0 -9
- data/lib/shared_tools/raix/what_is_the_weather.rb +0 -18
- data/lib/shared_tools/raix.rb +0 -9
- data/lib/shared_tools/ruby_llm/edit_file.rb +0 -71
- data/lib/shared_tools/ruby_llm/incomplete/calculator_tool.rb +0 -70
- data/lib/shared_tools/ruby_llm/incomplete/composite_analysis_tool.rb +0 -89
- data/lib/shared_tools/ruby_llm/incomplete/data_science_kit.rb +0 -128
- data/lib/shared_tools/ruby_llm/incomplete/database_query_tool.rb +0 -100
- data/lib/shared_tools/ruby_llm/incomplete/devops_toolkit.rb +0 -112
- data/lib/shared_tools/ruby_llm/incomplete/error_handling_tool.rb +0 -109
- data/lib/shared_tools/ruby_llm/incomplete/secure_tool_template.rb +0 -117
- data/lib/shared_tools/ruby_llm/incomplete/weather_tool.rb +0 -110
- data/lib/shared_tools/ruby_llm/incomplete/workflow_manager_tool.rb +0 -145
- data/lib/shared_tools/ruby_llm/list_files.rb +0 -49
- data/lib/shared_tools/ruby_llm/mcp/imcp.rb +0 -33
- data/lib/shared_tools/ruby_llm/mcp.rb +0 -10
- data/lib/shared_tools/ruby_llm/pdf_page_reader.rb +0 -59
- data/lib/shared_tools/ruby_llm/python_eval.rb +0 -194
- data/lib/shared_tools/ruby_llm/read_file.rb +0 -40
- data/lib/shared_tools/ruby_llm/ruby_eval.rb +0 -77
- data/lib/shared_tools/ruby_llm/run_shell_command.rb +0 -49
- 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
|