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,56 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ruby_llm/tool'
3
+ require_relative '../../shared_tools'
4
4
 
5
5
  module SharedTools
6
6
  module Tools
7
- # A tool for retrieving system information including OS, CPU, memory, and disk details.
8
- # Provides cross-platform support for macOS, Linux, and Windows.
7
+ # Returns OS, CPU, memory, disk, network, and Ruby runtime information.
9
8
  #
10
9
  # @example
11
10
  # tool = SharedTools::Tools::SystemInfoTool.new
12
- # result = tool.execute(category: 'all')
13
- # puts result[:os][:name] # "macOS"
14
- # puts result[:memory][:total] # "32 GB"
15
- class SystemInfoTool < RubyLLM::Tool
16
- def self.name = 'system_info'
11
+ # tool.execute # all categories
12
+ # tool.execute(category: 'cpu') # CPU only
13
+ class SystemInfoTool < ::RubyLLM::Tool
14
+ def self.name = 'system_info_tool'
17
15
 
18
- description <<~'DESCRIPTION'
19
- Retrieve system information including operating system, CPU, memory, and disk details.
20
-
21
- This tool provides cross-platform system information:
22
- - macOS: Uses system_profiler, sysctl, and df commands
23
- - Linux: Uses /proc filesystem and df command
24
- - Windows: Uses wmic and powershell commands
16
+ description <<~DESC
17
+ Retrieve system information from the local machine.
25
18
 
26
19
  Categories:
27
- - 'all': Returns all system information (default)
28
- - 'os': Operating system information only
29
- - 'cpu': CPU information only
30
- - 'memory': Memory information only
31
- - 'disk': Disk space information only
32
- - 'network': Network interface information only
33
-
34
- Example usage:
35
- tool = SharedTools::Tools::SystemInfoTool.new
36
-
37
- # Get all system info
38
- result = tool.execute(category: 'all')
39
-
40
- # Get specific category
41
- result = tool.execute(category: 'memory')
42
- puts result[:total] # Total RAM
43
- DESCRIPTION
20
+ - 'os' Operating system name, version, hostname
21
+ - 'cpu' CPU model, core count, load averages
22
+ - 'memory' Total and available RAM in GB
23
+ - 'disk' Mounted filesystems with used/available space
24
+ - 'network' Active network interfaces and their IP addresses
25
+ - 'ruby' Ruby version, platform, engine, RubyGems version
26
+ - 'all' (default) — All of the above combined
27
+ DESC
44
28
 
45
29
  params do
46
- string :category, description: <<~DESC.strip, required: false
47
- The category of system information to retrieve:
48
- - 'all' (default): All system information
49
- - 'os': Operating system details
50
- - 'cpu': CPU details
51
- - 'memory': Memory/RAM details
52
- - 'disk': Disk space details
53
- - 'network': Network interface details
30
+ string :category, required: false, description: <<~DESC.strip
31
+ Info category. Options: 'os', 'cpu', 'memory', 'disk', 'network', 'ruby', 'all' (default).
54
32
  DESC
55
33
  end
56
34
 
@@ -59,358 +37,167 @@ module SharedTools
59
37
  @logger = logger || RubyLLM.logger
60
38
  end
61
39
 
62
- # Execute system info retrieval
63
- #
64
- # @param category [String] category of info to retrieve
40
+ # @param category [String] which subsystem to query
65
41
  # @return [Hash] system information
66
42
  def execute(category: 'all')
67
- @logger.info("SystemInfoTool#execute category=#{category.inspect}")
43
+ @logger.info("SystemInfoTool#execute category=#{category}")
68
44
 
69
45
  case category.to_s.downcase
70
- when 'all'
71
- get_all_info
72
- when 'os'
73
- { success: true, os: get_os_info }
74
- when 'cpu'
75
- { success: true, cpu: get_cpu_info }
76
- when 'memory'
77
- { success: true, memory: get_memory_info }
78
- when 'disk'
79
- { success: true, disk: get_disk_info }
80
- when 'network'
81
- { success: true, network: get_network_info }
46
+ when 'os' then { success: true }.merge(os_info)
47
+ when 'cpu' then { success: true }.merge(cpu_info)
48
+ when 'memory' then { success: true }.merge(memory_info)
49
+ when 'disk' then { success: true }.merge(disk_info)
50
+ when 'network' then { success: true }.merge(network_info)
51
+ when 'ruby' then { success: true }.merge(ruby_info)
82
52
  else
83
- {
84
- success: false,
85
- error: "Unknown category: #{category}. Valid categories are: all, os, cpu, memory, disk, network"
86
- }
53
+ { success: true }
54
+ .merge(os_info)
55
+ .merge(cpu_info)
56
+ .merge(memory_info)
57
+ .merge(disk_info)
58
+ .merge(network_info)
59
+ .merge(ruby_info)
87
60
  end
88
61
  rescue => e
89
62
  @logger.error("SystemInfoTool error: #{e.message}")
90
- {
91
- success: false,
92
- error: e.message
93
- }
63
+ { success: false, error: e.message }
94
64
  end
95
65
 
96
66
  private
97
67
 
98
- def get_all_info
68
+ def os_info
99
69
  {
100
- success: true,
101
- os: get_os_info,
102
- cpu: get_cpu_info,
103
- memory: get_memory_info,
104
- disk: get_disk_info,
105
- network: get_network_info,
106
- ruby: get_ruby_info
70
+ os_platform: RUBY_PLATFORM,
71
+ os_name: detect_os_name,
72
+ os_version: detect_os_version,
73
+ hostname: `hostname`.strip
107
74
  }
108
75
  end
109
76
 
110
- def get_os_info
111
- case platform
112
- when :macos
113
- {
114
- name: 'macOS',
115
- version: `sw_vers -productVersion 2>/dev/null`.strip,
116
- build: `sw_vers -buildVersion 2>/dev/null`.strip,
117
- hostname: `hostname 2>/dev/null`.strip,
118
- kernel: `uname -r 2>/dev/null`.strip,
119
- architecture: `uname -m 2>/dev/null`.strip,
120
- uptime: parse_uptime(`uptime 2>/dev/null`.strip)
121
- }
122
- when :linux
123
- {
124
- name: get_linux_distro,
125
- version: get_linux_version,
126
- hostname: `hostname 2>/dev/null`.strip,
127
- kernel: `uname -r 2>/dev/null`.strip,
128
- architecture: `uname -m 2>/dev/null`.strip,
129
- uptime: parse_uptime(`uptime 2>/dev/null`.strip)
130
- }
131
- when :windows
132
- {
133
- name: 'Windows',
134
- version: `ver 2>nul`.strip,
135
- hostname: `hostname 2>nul`.strip,
136
- architecture: ENV['PROCESSOR_ARCHITECTURE'] || 'unknown'
137
- }
77
+ def cpu_info
78
+ if RUBY_PLATFORM.include?('darwin')
79
+ model = `sysctl -n machdep.cpu.brand_string 2>/dev/null`.strip
80
+ cores = `sysctl -n hw.ncpu 2>/dev/null`.strip.to_i
81
+ load = `sysctl -n vm.loadavg 2>/dev/null`.strip
82
+ .gsub(/[{}]/, '').split.first(3).map(&:to_f)
138
83
  else
139
- { name: 'unknown', platform: RUBY_PLATFORM }
84
+ model = File.read('/proc/cpuinfo')
85
+ .match(/model name\s*:\s*(.+)/)&.captures&.first&.strip rescue 'Unknown'
86
+ cores = `nproc 2>/dev/null`.strip.to_i
87
+ load = File.read('/proc/loadavg').split.first(3).map(&:to_f) rescue [0.0, 0.0, 0.0]
140
88
  end
89
+
90
+ {
91
+ cpu_model: model.empty? ? 'Unknown' : model,
92
+ cpu_cores: cores,
93
+ load_avg_1m: load[0],
94
+ load_avg_5m: load[1],
95
+ load_avg_15m: load[2]
96
+ }
141
97
  end
142
98
 
143
- def get_cpu_info
144
- case platform
145
- when :macos
146
- {
147
- model: `sysctl -n machdep.cpu.brand_string 2>/dev/null`.strip,
148
- cores: `sysctl -n hw.ncpu 2>/dev/null`.strip.to_i,
149
- physical_cores: `sysctl -n hw.physicalcpu 2>/dev/null`.strip.to_i,
150
- architecture: `uname -m 2>/dev/null`.strip,
151
- load_average: get_load_average
152
- }
153
- when :linux
154
- cpu_info = File.read('/proc/cpuinfo') rescue ''
155
- model = cpu_info[/model name\s*:\s*(.+)/, 1] || 'unknown'
156
- cores = cpu_info.scan(/^processor/i).count
157
- {
158
- model: model,
159
- cores: cores,
160
- architecture: `uname -m 2>/dev/null`.strip,
161
- load_average: get_load_average
162
- }
163
- when :windows
164
- {
165
- model: `wmic cpu get name 2>nul`.lines[1]&.strip || 'unknown',
166
- cores: ENV['NUMBER_OF_PROCESSORS']&.to_i || 0,
167
- architecture: ENV['PROCESSOR_ARCHITECTURE'] || 'unknown'
168
- }
99
+ def memory_info
100
+ if RUBY_PLATFORM.include?('darwin')
101
+ total = `sysctl -n hw.memsize 2>/dev/null`.strip.to_i
102
+ vm_stat = `vm_stat 2>/dev/null`
103
+ pg_size = vm_stat.match(/page size of (\d+) bytes/)&.captures&.first&.to_i || 4096
104
+ free_pg = vm_stat.match(/Pages free:\s+(\d+)/)&.captures&.first&.to_i || 0
105
+ inact_pg = vm_stat.match(/Pages inactive:\s+(\d+)/)&.captures&.first&.to_i || 0
106
+ available = (free_pg + inact_pg) * pg_size
169
107
  else
170
- { cores: 0, model: 'unknown' }
108
+ mem = File.read('/proc/meminfo') rescue ''
109
+ total = (mem.match(/MemTotal:\s+(\d+) kB/)&.captures&.first&.to_i || 0) * 1024
110
+ available = (mem.match(/MemAvailable:\s+(\d+) kB/)&.captures&.first&.to_i || 0) * 1024
171
111
  end
172
- end
173
-
174
- def get_memory_info
175
- case platform
176
- when :macos
177
- total_bytes = `sysctl -n hw.memsize 2>/dev/null`.strip.to_i
178
- # Get page size and memory statistics
179
- vm_stat = `vm_stat 2>/dev/null`
180
- page_size = vm_stat[/page size of (\d+)/, 1]&.to_i || 4096
181
- pages_free = vm_stat[/Pages free:\s+(\d+)/, 1]&.to_i || 0
182
- pages_inactive = vm_stat[/Pages inactive:\s+(\d+)/, 1]&.to_i || 0
183
-
184
- available_bytes = (pages_free + pages_inactive) * page_size
185
- used_bytes = total_bytes - available_bytes
186
112
 
187
- {
188
- total: format_bytes(total_bytes),
189
- total_bytes: total_bytes,
190
- available: format_bytes(available_bytes),
191
- available_bytes: available_bytes,
192
- used: format_bytes(used_bytes),
193
- used_bytes: used_bytes,
194
- percent_used: ((used_bytes.to_f / total_bytes) * 100).round(1)
195
- }
196
- when :linux
197
- meminfo = File.read('/proc/meminfo') rescue ''
198
- total_kb = meminfo[/MemTotal:\s+(\d+)/, 1]&.to_i || 0
199
- available_kb = meminfo[/MemAvailable:\s+(\d+)/, 1]&.to_i || 0
200
- used_kb = total_kb - available_kb
113
+ gb = 1024.0**3
114
+ {
115
+ memory_total_gb: (total / gb).round(2),
116
+ memory_available_gb: (available / gb).round(2),
117
+ memory_used_gb: ((total - available) / gb).round(2)
118
+ }
119
+ end
201
120
 
202
- {
203
- total: format_bytes(total_kb * 1024),
204
- total_bytes: total_kb * 1024,
205
- available: format_bytes(available_kb * 1024),
206
- available_bytes: available_kb * 1024,
207
- used: format_bytes(used_kb * 1024),
208
- used_bytes: used_kb * 1024,
209
- percent_used: total_kb > 0 ? ((used_kb.to_f / total_kb) * 100).round(1) : 0
210
- }
211
- when :windows
212
- # Using powershell for more reliable output
213
- output = `powershell -command "Get-CimInstance Win32_OperatingSystem | Select-Object TotalVisibleMemorySize,FreePhysicalMemory" 2>nul`
214
- total_kb = output[/TotalVisibleMemorySize\s*:\s*(\d+)/, 1]&.to_i || 0
215
- free_kb = output[/FreePhysicalMemory\s*:\s*(\d+)/, 1]&.to_i || 0
216
- used_kb = total_kb - free_kb
121
+ def disk_info
122
+ lines = `df -k 2>/dev/null`.lines.drop(1)
123
+ mounts = lines.filter_map do |line|
124
+ parts = line.split
125
+ next unless parts.size >= 6
217
126
 
127
+ kb = 1024.0**2
218
128
  {
219
- total: format_bytes(total_kb * 1024),
220
- total_bytes: total_kb * 1024,
221
- available: format_bytes(free_kb * 1024),
222
- available_bytes: free_kb * 1024,
223
- used: format_bytes(used_kb * 1024),
224
- used_bytes: used_kb * 1024,
225
- percent_used: total_kb > 0 ? ((used_kb.to_f / total_kb) * 100).round(1) : 0
129
+ filesystem: parts[0],
130
+ mount_point: parts[5],
131
+ size_gb: (parts[1].to_i / kb).round(2),
132
+ used_gb: (parts[2].to_i / kb).round(2),
133
+ available_gb: (parts[3].to_i / kb).round(2),
134
+ use_percent: parts[4]
226
135
  }
227
- else
228
- { total: 'unknown', available: 'unknown' }
229
136
  end
137
+ { disks: mounts }
230
138
  end
231
139
 
232
- def get_disk_info
233
- disks = []
140
+ def network_info
141
+ interfaces = {}
234
142
 
235
- case platform
236
- when :macos, :linux
237
- df_output = `df -h 2>/dev/null`.lines[1..]
238
- df_output&.each do |line|
239
- parts = line.split
240
- next if parts.length < 6
241
- next unless parts[0].start_with?('/') || parts[5]&.start_with?('/')
242
-
243
- mount_point = parts[5] || parts[0]
244
- disks << {
245
- filesystem: parts[0],
246
- size: parts[1],
247
- used: parts[2],
248
- available: parts[3],
249
- percent_used: parts[4],
250
- mount_point: mount_point
251
- }
252
- end
253
- when :windows
254
- output = `wmic logicaldisk get size,freespace,caption 2>nul`
255
- output.lines[1..].each do |line|
256
- parts = line.split
257
- next if parts.length < 3
258
-
259
- caption, free_space, size = parts
260
- next unless size.to_i > 0
261
-
262
- disks << {
263
- filesystem: caption,
264
- size: format_bytes(size.to_i),
265
- available: format_bytes(free_space.to_i),
266
- used: format_bytes(size.to_i - free_space.to_i),
267
- percent_used: "#{((1 - free_space.to_f / size.to_i) * 100).round}%"
268
- }
269
- end
270
- end
271
-
272
- disks
273
- end
274
-
275
- def get_network_info
276
- interfaces = []
277
-
278
- case platform
279
- when :macos
280
- ifconfig = `ifconfig 2>/dev/null`
281
- current_interface = nil
282
-
283
- ifconfig.each_line do |line|
284
- if line =~ /^(\w+):/
285
- current_interface = { name: $1, addresses: [] }
286
- interfaces << current_interface
287
- elsif current_interface && line =~ /inet (\d+\.\d+\.\d+\.\d+)/
288
- current_interface[:addresses] << { type: 'IPv4', address: $1 }
289
- elsif current_interface && line =~ /inet6 ([a-f0-9:]+)/
290
- current_interface[:addresses] << { type: 'IPv6', address: $1 }
291
- end
292
- end
293
- when :linux
294
- # Try ip command first, fall back to ifconfig
295
- output = `ip addr 2>/dev/null`
296
- if output.empty?
297
- output = `ifconfig 2>/dev/null`
298
- end
299
-
300
- current_interface = nil
301
- output.each_line do |line|
302
- if line =~ /^\d+:\s+(\w+):/
303
- current_interface = { name: $1, addresses: [] }
304
- interfaces << current_interface
305
- elsif line =~ /^(\w+):/
306
- current_interface = { name: $1, addresses: [] }
307
- interfaces << current_interface
308
- elsif current_interface && line =~ /inet (\d+\.\d+\.\d+\.\d+)/
309
- current_interface[:addresses] << { type: 'IPv4', address: $1 }
310
- elsif current_interface && line =~ /inet6 ([a-f0-9:]+)/
311
- current_interface[:addresses] << { type: 'IPv6', address: $1 }
143
+ if RUBY_PLATFORM.include?('darwin')
144
+ current = nil
145
+ `ifconfig 2>/dev/null`.lines.each do |line|
146
+ if (m = line.match(/^(\w[\w:]+\d+):/))
147
+ current = m.captures.first
148
+ interfaces[current] = []
149
+ elsif current && (m = line.match(/\s+inet6?\s+(\S+)/))
150
+ addr = m.captures.first.split('%').first
151
+ interfaces[current] << addr
312
152
  end
313
153
  end
314
- when :windows
315
- output = `ipconfig 2>nul`
316
- current_interface = nil
317
-
318
- output.each_line do |line|
319
- if line =~ /adapter (.+):/i
320
- current_interface = { name: $1.strip, addresses: [] }
321
- interfaces << current_interface
322
- elsif current_interface && line =~ /IPv4.*:\s*(\d+\.\d+\.\d+\.\d+)/
323
- current_interface[:addresses] << { type: 'IPv4', address: $1 }
324
- elsif current_interface && line =~ /IPv6.*:\s*([a-f0-9:]+)/i
325
- current_interface[:addresses] << { type: 'IPv6', address: $1 }
154
+ else
155
+ current = nil
156
+ `ip addr 2>/dev/null`.lines.each do |line|
157
+ if (m = line.match(/^\d+: (\w+):/))
158
+ current = m.captures.first
159
+ interfaces[current] = []
160
+ elsif current && (m = line.match(/\s+inet6?\s+(\S+)/))
161
+ interfaces[current] << m.captures.first.split('/').first
326
162
  end
327
163
  end
328
164
  end
329
165
 
330
- # Filter out interfaces with no addresses
331
- interfaces.select { |i| !i[:addresses].empty? }
166
+ { network_interfaces: interfaces.reject { |_, ips| ips.empty? } }
332
167
  end
333
168
 
334
- def get_ruby_info
169
+ def ruby_info
335
170
  {
336
- version: RUBY_VERSION,
337
- platform: RUBY_PLATFORM,
338
- engine: RUBY_ENGINE,
339
- engine_version: RUBY_ENGINE_VERSION,
340
- patchlevel: RUBY_PATCHLEVEL
171
+ ruby_version: RUBY_VERSION,
172
+ ruby_platform: RUBY_PLATFORM,
173
+ ruby_engine: RUBY_ENGINE,
174
+ ruby_description: RUBY_DESCRIPTION,
175
+ rubygems_version: Gem::VERSION
341
176
  }
342
177
  end
343
178
 
344
- def platform
345
- case RUBY_PLATFORM
346
- when /darwin/
347
- :macos
348
- when /linux/
349
- :linux
350
- when /mswin|mingw|cygwin/
351
- :windows
352
- else
353
- :unknown
354
- end
355
- end
356
-
357
- def format_bytes(bytes)
358
- return '0 B' if bytes.nil? || bytes == 0
359
-
360
- units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
361
- exp = (Math.log(bytes) / Math.log(1024)).to_i
362
- exp = units.length - 1 if exp >= units.length
363
-
364
- "#{(bytes.to_f / (1024**exp)).round(2)} #{units[exp]}"
365
- end
366
-
367
- def get_load_average
368
- case platform
369
- when :macos, :linux
370
- uptime_output = `uptime 2>/dev/null`
371
- if uptime_output =~ /load average[s]?:\s*([\d.]+),?\s*([\d.]+),?\s*([\d.]+)/
372
- { '1min' => $1.to_f, '5min' => $2.to_f, '15min' => $3.to_f }
373
- else
374
- {}
375
- end
376
- else
377
- {}
378
- end
379
- end
380
-
381
- def parse_uptime(uptime_str)
382
- # Extract uptime portion from the uptime command output
383
- if uptime_str =~ /up\s+(.+?),\s+\d+\s+user/
384
- $1.strip
385
- elsif uptime_str =~ /up\s+(.+)/
386
- $1.split(',').first.strip
387
- else
388
- uptime_str
389
- end
390
- end
391
-
392
- def get_linux_distro
393
- if File.exist?('/etc/os-release')
394
- content = File.read('/etc/os-release')
395
- content[/^NAME="?([^"\n]+)"?/, 1] || 'Linux'
396
- elsif File.exist?('/etc/lsb-release')
397
- content = File.read('/etc/lsb-release')
398
- content[/DISTRIB_ID=(.+)/, 1] || 'Linux'
179
+ def detect_os_name
180
+ if RUBY_PLATFORM.include?('darwin')
181
+ `sw_vers -productName 2>/dev/null`.strip
182
+ elsif File.exist?('/etc/os-release')
183
+ File.read('/etc/os-release').match(/^NAME="?([^"\n]+)"?/)&.captures&.first || 'Linux'
399
184
  else
400
- 'Linux'
185
+ 'Unknown'
401
186
  end
187
+ rescue
188
+ 'Unknown'
402
189
  end
403
190
 
404
- def get_linux_version
405
- if File.exist?('/etc/os-release')
406
- content = File.read('/etc/os-release')
407
- content[/^VERSION="?([^"\n]+)"?/, 1] || ''
408
- elsif File.exist?('/etc/lsb-release')
409
- content = File.read('/etc/lsb-release')
410
- content[/DISTRIB_RELEASE=(.+)/, 1] || ''
191
+ def detect_os_version
192
+ if RUBY_PLATFORM.include?('darwin')
193
+ `sw_vers -productVersion 2>/dev/null`.strip
194
+ elsif File.exist?('/etc/os-release')
195
+ File.read('/etc/os-release').match(/^VERSION="?([^"\n]+)"?/)&.captures&.first || 'Unknown'
411
196
  else
412
- ''
197
+ 'Unknown'
413
198
  end
199
+ rescue
200
+ 'Unknown'
414
201
  end
415
202
  end
416
203
  end
@@ -34,6 +34,7 @@ module SharedTools
34
34
  - 'step': Execute the next step in an existing workflow using provided step data
35
35
  - 'status': Check the current status and progress of an existing workflow
36
36
  - 'complete': Mark a workflow as finished and clean up associated resources
37
+ - 'list': List all existing workflows with their current status and summary information
37
38
  Each action requires different combinations of the other parameters.
38
39
  DESC
39
40
 
@@ -84,6 +85,8 @@ module SharedTools
84
85
  when "complete"
85
86
  return {success: false, error: "workflow_id required for 'complete' action"} unless workflow_id
86
87
  complete_workflow(workflow_id)
88
+ when "list"
89
+ list_workflows
87
90
  else
88
91
  {success: false, error: "Unknown action: #{action}"}
89
92
  end
@@ -279,6 +282,35 @@ module SharedTools
279
282
  }
280
283
  end
281
284
 
285
+ # List all workflows in the storage directory
286
+ def list_workflows
287
+ pattern = File.join(@storage_dir, "workflow_*.json")
288
+ files = Dir.glob(pattern)
289
+
290
+ workflows = files.filter_map do |file|
291
+ state = JSON.parse(File.read(file), symbolize_names: true)
292
+ {
293
+ workflow_id: state[:id],
294
+ status: state[:status],
295
+ step_count: state[:steps]&.length || 0,
296
+ created_at: state[:created_at],
297
+ updated_at: state[:updated_at],
298
+ completed_at: state[:completed_at]
299
+ }
300
+ rescue => e
301
+ @logger.warn("Skipping unreadable workflow file #{file}: #{e.message}")
302
+ nil
303
+ end
304
+
305
+ workflows.sort_by! { |w| w[:created_at] || "" }
306
+
307
+ {
308
+ success: true,
309
+ total: workflows.size,
310
+ workflows: workflows
311
+ }
312
+ end
313
+
282
314
  # Save workflow state to disk
283
315
  def save_workflow_state(workflow_id, state)
284
316
  file_path = workflow_file_path(workflow_id)