enhance_swarm 1.0.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 +7 -0
- data/.enhance_swarm/agent_scripts/frontend_agent.md +39 -0
- data/.enhance_swarm/user_patterns.json +37 -0
- data/CHANGELOG.md +184 -0
- data/LICENSE +21 -0
- data/PRODUCTION_TEST_LOG.md +502 -0
- data/README.md +905 -0
- data/Rakefile +28 -0
- data/USAGE_EXAMPLES.md +477 -0
- data/examples/enhance_workflow.md +346 -0
- data/examples/rails_project.md +253 -0
- data/exe/enhance-swarm +30 -0
- data/lib/enhance_swarm/additional_commands.rb +299 -0
- data/lib/enhance_swarm/agent_communicator.rb +460 -0
- data/lib/enhance_swarm/agent_reviewer.rb +283 -0
- data/lib/enhance_swarm/agent_spawner.rb +462 -0
- data/lib/enhance_swarm/cleanup_manager.rb +245 -0
- data/lib/enhance_swarm/cli.rb +1592 -0
- data/lib/enhance_swarm/command_executor.rb +78 -0
- data/lib/enhance_swarm/configuration.rb +324 -0
- data/lib/enhance_swarm/control_agent.rb +307 -0
- data/lib/enhance_swarm/dependency_validator.rb +195 -0
- data/lib/enhance_swarm/error_recovery.rb +785 -0
- data/lib/enhance_swarm/generator.rb +194 -0
- data/lib/enhance_swarm/interrupt_handler.rb +512 -0
- data/lib/enhance_swarm/logger.rb +106 -0
- data/lib/enhance_swarm/mcp_integration.rb +85 -0
- data/lib/enhance_swarm/monitor.rb +28 -0
- data/lib/enhance_swarm/notification_manager.rb +444 -0
- data/lib/enhance_swarm/orchestrator.rb +313 -0
- data/lib/enhance_swarm/output_streamer.rb +281 -0
- data/lib/enhance_swarm/process_monitor.rb +266 -0
- data/lib/enhance_swarm/progress_tracker.rb +215 -0
- data/lib/enhance_swarm/project_analyzer.rb +612 -0
- data/lib/enhance_swarm/resource_manager.rb +177 -0
- data/lib/enhance_swarm/retry_handler.rb +40 -0
- data/lib/enhance_swarm/session_manager.rb +247 -0
- data/lib/enhance_swarm/signal_handler.rb +95 -0
- data/lib/enhance_swarm/smart_defaults.rb +708 -0
- data/lib/enhance_swarm/task_integration.rb +150 -0
- data/lib/enhance_swarm/task_manager.rb +174 -0
- data/lib/enhance_swarm/version.rb +5 -0
- data/lib/enhance_swarm/visual_dashboard.rb +555 -0
- data/lib/enhance_swarm/web_ui.rb +211 -0
- data/lib/enhance_swarm.rb +69 -0
- data/setup.sh +86 -0
- data/sig/enhance_swarm.rbs +4 -0
- data/templates/claude/CLAUDE.md +160 -0
- data/templates/claude/MCP.md +117 -0
- data/templates/claude/PERSONAS.md +114 -0
- data/templates/claude/RULES.md +221 -0
- data/test_builtin_functionality.rb +121 -0
- data/test_core_components.rb +156 -0
- data/test_real_claude_integration.rb +285 -0
- data/test_security.rb +150 -0
- data/test_smart_defaults.rb +155 -0
- data/test_task_integration.rb +173 -0
- data/test_web_ui.rb +245 -0
- data/web/assets/css/main.css +645 -0
- data/web/assets/js/kanban.js +499 -0
- data/web/assets/js/main.js +525 -0
- data/web/templates/dashboard.html.erb +226 -0
- data/web/templates/kanban.html.erb +193 -0
- metadata +293 -0
@@ -0,0 +1,555 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
require 'colorize'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module EnhanceSwarm
|
8
|
+
class VisualDashboard
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@agents = {}
|
13
|
+
@coordination_status = {}
|
14
|
+
@dashboard_active = false
|
15
|
+
@refresh_rate = 2 # seconds
|
16
|
+
@last_update = Time.now
|
17
|
+
@terminal_size = get_terminal_size
|
18
|
+
end
|
19
|
+
|
20
|
+
# Start the visual dashboard
|
21
|
+
def start_dashboard(agents = [])
|
22
|
+
@dashboard_active = true
|
23
|
+
@agents = agents.each_with_object({}) { |agent, hash| hash[agent[:id]] = agent }
|
24
|
+
|
25
|
+
puts "š„ļø Starting Visual Agent Dashboard...".colorize(:green)
|
26
|
+
puts "Press 'q' to quit, 'r' to refresh, 'p' to pause".colorize(:light_black)
|
27
|
+
|
28
|
+
setup_terminal
|
29
|
+
display_loop
|
30
|
+
end
|
31
|
+
|
32
|
+
def stop_dashboard
|
33
|
+
@dashboard_active = false
|
34
|
+
restore_terminal
|
35
|
+
puts "\nš„ļø Dashboard stopped".colorize(:yellow)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Update agent status
|
39
|
+
def update_agent(agent_id, updates)
|
40
|
+
return unless @agents[agent_id]
|
41
|
+
|
42
|
+
@agents[agent_id].merge!(updates)
|
43
|
+
@last_update = Time.now
|
44
|
+
end
|
45
|
+
|
46
|
+
# Update coordination status
|
47
|
+
def update_coordination(status)
|
48
|
+
@coordination_status = status
|
49
|
+
@last_update = Time.now
|
50
|
+
end
|
51
|
+
|
52
|
+
# Add new agent to dashboard
|
53
|
+
def add_agent(agent)
|
54
|
+
@agents[agent[:id]] = agent
|
55
|
+
@last_update = Time.now
|
56
|
+
end
|
57
|
+
|
58
|
+
# Remove agent from dashboard
|
59
|
+
def remove_agent(agent_id)
|
60
|
+
@agents.delete(agent_id)
|
61
|
+
@last_update = Time.now
|
62
|
+
end
|
63
|
+
|
64
|
+
# Display a static snapshot of agent status
|
65
|
+
def display_snapshot(agents = [])
|
66
|
+
@agents = agents.each_with_object({}) { |agent, hash| hash[agent[:id]] = agent }
|
67
|
+
|
68
|
+
puts "šø EnhanceSwarm Dashboard Snapshot".colorize(:cyan)
|
69
|
+
puts "ā" * 50
|
70
|
+
puts "Timestamp: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
71
|
+
puts
|
72
|
+
|
73
|
+
if @agents.empty?
|
74
|
+
puts "No agents currently running".colorize(:light_black)
|
75
|
+
return
|
76
|
+
end
|
77
|
+
|
78
|
+
puts "š¤ Agent Status:".colorize(:blue)
|
79
|
+
@agents.each do |id, agent|
|
80
|
+
role = agent[:role] || 'unknown'
|
81
|
+
status = format_agent_status(agent)
|
82
|
+
progress = agent[:progress_percentage] || agent[:progress] || 0
|
83
|
+
duration = format_duration(agent)
|
84
|
+
|
85
|
+
puts " #{role.ljust(10)} ā #{status} ā #{progress}% ā #{duration}"
|
86
|
+
end
|
87
|
+
|
88
|
+
puts
|
89
|
+
puts "System Resources:".colorize(:blue)
|
90
|
+
memory_info = get_memory_info
|
91
|
+
puts " Memory: #{memory_info[:used_gb]}GB/#{memory_info[:total_gb]}GB (#{memory_info[:used_percent]}%)"
|
92
|
+
puts " Active Processes: #{@agents.count { |_, agent| agent[:pid] }}"
|
93
|
+
|
94
|
+
puts "\nš Summary:".colorize(:green)
|
95
|
+
puts " Total Agents: #{@agents.count}"
|
96
|
+
puts " Active: #{@agents.count { |_, agent| agent[:status] == 'active' || agent[:status] == 'running' }}"
|
97
|
+
puts " Completed: #{@agents.count { |_, agent| agent[:status] == 'completed' }}"
|
98
|
+
puts " Failed: #{@agents.count { |_, agent| agent[:status] == 'failed' }}"
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def setup_terminal
|
104
|
+
# Hide cursor and enable raw mode for input
|
105
|
+
print "\e[?25l" # Hide cursor
|
106
|
+
print "\e[2J" # Clear screen
|
107
|
+
print "\e[H" # Move to top-left
|
108
|
+
end
|
109
|
+
|
110
|
+
def restore_terminal
|
111
|
+
print "\e[?25h" # Show cursor
|
112
|
+
print "\e[0m" # Reset colors
|
113
|
+
end
|
114
|
+
|
115
|
+
def display_loop
|
116
|
+
while @dashboard_active
|
117
|
+
render_dashboard
|
118
|
+
|
119
|
+
# Check for user input (non-blocking)
|
120
|
+
if input_available?
|
121
|
+
key = $stdin.getc
|
122
|
+
handle_input(key)
|
123
|
+
end
|
124
|
+
|
125
|
+
sleep(@refresh_rate)
|
126
|
+
end
|
127
|
+
rescue Interrupt
|
128
|
+
@dashboard_active = false
|
129
|
+
ensure
|
130
|
+
restore_terminal
|
131
|
+
end
|
132
|
+
|
133
|
+
def render_dashboard
|
134
|
+
clear_screen
|
135
|
+
|
136
|
+
# Header
|
137
|
+
render_header
|
138
|
+
|
139
|
+
# Coordination overview
|
140
|
+
render_coordination_overview
|
141
|
+
|
142
|
+
# Agent grid
|
143
|
+
render_agent_grid
|
144
|
+
|
145
|
+
# Status bar
|
146
|
+
render_status_bar
|
147
|
+
|
148
|
+
# Controls
|
149
|
+
render_controls
|
150
|
+
end
|
151
|
+
|
152
|
+
def clear_screen
|
153
|
+
print "\e[2J\e[H"
|
154
|
+
end
|
155
|
+
|
156
|
+
def render_header
|
157
|
+
time_str = Time.now.strftime('%H:%M:%S')
|
158
|
+
agent_count = @agents.count
|
159
|
+
active_count = @agents.count { |_, agent| agent[:status] == 'active' }
|
160
|
+
|
161
|
+
puts "āāāā š„ļø EnhanceSwarm Visual Dashboard āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā".colorize(:cyan)
|
162
|
+
puts "ā #{time_str} ā Agents: #{agent_count} ā Active: #{active_count} ā Updated: #{time_ago(@last_update)} ago ā".colorize(:white)
|
163
|
+
puts "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā".colorize(:cyan)
|
164
|
+
puts
|
165
|
+
end
|
166
|
+
|
167
|
+
def render_coordination_overview
|
168
|
+
puts "š Coordination Status".colorize(:blue)
|
169
|
+
puts "ā" * 40
|
170
|
+
|
171
|
+
if @coordination_status.any?
|
172
|
+
phase = @coordination_status[:phase] || 'Unknown'
|
173
|
+
progress = @coordination_status[:progress] || 0
|
174
|
+
active_agents = @coordination_status[:active_agents] || []
|
175
|
+
completed_agents = @coordination_status[:completed_agents] || []
|
176
|
+
|
177
|
+
puts "Phase: #{phase}".colorize(:yellow)
|
178
|
+
puts "Progress: #{render_progress_bar(progress, 30)} #{progress}%"
|
179
|
+
puts "Active: #{active_agents.join(', ')}" if active_agents.any?
|
180
|
+
puts "Completed: #{completed_agents.join(', ')}" if completed_agents.any?
|
181
|
+
else
|
182
|
+
puts "No active coordination".colorize(:light_black)
|
183
|
+
end
|
184
|
+
|
185
|
+
puts
|
186
|
+
end
|
187
|
+
|
188
|
+
def render_agent_grid
|
189
|
+
puts "š¤ Agent Status Grid".colorize(:blue)
|
190
|
+
puts "ā" * 60
|
191
|
+
|
192
|
+
if @agents.empty?
|
193
|
+
puts "No agents to display".colorize(:light_black)
|
194
|
+
return
|
195
|
+
end
|
196
|
+
|
197
|
+
# Calculate grid layout
|
198
|
+
terminal_width = @terminal_size[:width] || 80
|
199
|
+
agent_width = 18
|
200
|
+
cols = [terminal_width / agent_width, 1].max
|
201
|
+
|
202
|
+
@agents.values.each_slice(cols) do |agent_row|
|
203
|
+
render_agent_row(agent_row)
|
204
|
+
puts
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def render_agent_row(agents)
|
209
|
+
# Top border
|
210
|
+
agents.each { print "āāāāāāāāāāāāāāāāāā " }
|
211
|
+
puts
|
212
|
+
|
213
|
+
# Agent ID and role
|
214
|
+
agents.each do |agent|
|
215
|
+
role = (agent[:role] || 'unknown')[0..7].ljust(8)
|
216
|
+
print "ā #{role} ā "
|
217
|
+
end
|
218
|
+
puts
|
219
|
+
|
220
|
+
# Status line
|
221
|
+
agents.each do |agent|
|
222
|
+
status = format_agent_status(agent)
|
223
|
+
print "ā #{status.ljust(14)} ā "
|
224
|
+
end
|
225
|
+
puts
|
226
|
+
|
227
|
+
# Progress line
|
228
|
+
agents.each do |agent|
|
229
|
+
progress = render_agent_progress(agent)
|
230
|
+
print "ā #{progress} ā "
|
231
|
+
end
|
232
|
+
puts
|
233
|
+
|
234
|
+
# Duration line
|
235
|
+
agents.each do |agent|
|
236
|
+
duration = format_duration(agent)
|
237
|
+
print "ā #{duration.ljust(14)} ā "
|
238
|
+
end
|
239
|
+
puts
|
240
|
+
|
241
|
+
# Bottom border
|
242
|
+
agents.each { print "āāāāāāāāāāāāāāāāāā " }
|
243
|
+
puts
|
244
|
+
end
|
245
|
+
|
246
|
+
def format_agent_status(agent)
|
247
|
+
status = agent[:status] || 'unknown'
|
248
|
+
|
249
|
+
case status
|
250
|
+
when 'active'
|
251
|
+
"š¢ Active".colorize(:green)
|
252
|
+
when 'completed'
|
253
|
+
"ā
Done".colorize(:green)
|
254
|
+
when 'failed'
|
255
|
+
"ā Failed".colorize(:red)
|
256
|
+
when 'stuck'
|
257
|
+
"ā ļø Stuck".colorize(:yellow)
|
258
|
+
when 'starting'
|
259
|
+
"š Starting".colorize(:blue)
|
260
|
+
else
|
261
|
+
"āŖ #{status.capitalize}".colorize(:light_black)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def render_agent_progress(agent)
|
266
|
+
if agent[:progress_percentage]
|
267
|
+
percentage = agent[:progress_percentage].to_i
|
268
|
+
bar = render_mini_progress_bar(percentage, 12)
|
269
|
+
"#{bar} #{percentage}%"
|
270
|
+
elsif agent[:current_task]
|
271
|
+
task = agent[:current_task][0..11]
|
272
|
+
task.ljust(14)
|
273
|
+
else
|
274
|
+
" "
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def format_duration(agent)
|
279
|
+
if agent[:start_time]
|
280
|
+
duration = Time.now - Time.parse(agent[:start_time])
|
281
|
+
format_time_duration(duration)
|
282
|
+
else
|
283
|
+
""
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def render_status_bar
|
288
|
+
puts "š System Resources".colorize(:blue)
|
289
|
+
puts "ā" * 30
|
290
|
+
|
291
|
+
# Memory usage
|
292
|
+
memory_info = get_memory_info
|
293
|
+
puts "Memory: #{render_progress_bar(memory_info[:used_percent], 20)} #{memory_info[:used_gb]}GB/#{memory_info[:total_gb]}GB"
|
294
|
+
|
295
|
+
# Active processes
|
296
|
+
process_count = @agents.count { |_, agent| agent[:pid] }
|
297
|
+
puts "Processes: #{process_count} active"
|
298
|
+
|
299
|
+
# Communication queue
|
300
|
+
if defined?(AgentCommunicator)
|
301
|
+
pending_messages = AgentCommunicator.instance.pending_messages.count
|
302
|
+
puts "Messages: #{pending_messages} pending"
|
303
|
+
end
|
304
|
+
|
305
|
+
puts
|
306
|
+
end
|
307
|
+
|
308
|
+
def render_controls
|
309
|
+
puts "š® Controls".colorize(:blue)
|
310
|
+
puts "ā" * 15
|
311
|
+
puts "[q] Quit [r] Refresh [p] Pause [c] Clear [h] Help"
|
312
|
+
end
|
313
|
+
|
314
|
+
def render_progress_bar(percentage, width)
|
315
|
+
filled = (percentage * width / 100.0).round
|
316
|
+
empty = width - filled
|
317
|
+
|
318
|
+
bar = "ā" * filled + "ā" * empty
|
319
|
+
case percentage
|
320
|
+
when 0..30
|
321
|
+
bar.colorize(:red)
|
322
|
+
when 31..70
|
323
|
+
bar.colorize(:yellow)
|
324
|
+
else
|
325
|
+
bar.colorize(:green)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def render_mini_progress_bar(percentage, width)
|
330
|
+
filled = (percentage * width / 100.0).round
|
331
|
+
empty = width - filled
|
332
|
+
|
333
|
+
"ā" * filled + "ā" * empty
|
334
|
+
end
|
335
|
+
|
336
|
+
def handle_input(key)
|
337
|
+
case key.downcase
|
338
|
+
when 'q'
|
339
|
+
@dashboard_active = false
|
340
|
+
when 'r'
|
341
|
+
# Force refresh
|
342
|
+
@last_update = Time.now
|
343
|
+
when 'p'
|
344
|
+
pause_dashboard
|
345
|
+
when 'c'
|
346
|
+
clear_screen
|
347
|
+
when 'h'
|
348
|
+
show_help
|
349
|
+
when 's'
|
350
|
+
save_dashboard_snapshot
|
351
|
+
when 'd'
|
352
|
+
show_detailed_view
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
def pause_dashboard
|
357
|
+
puts "\nāøļø Dashboard paused. Press any key to continue...".colorize(:yellow)
|
358
|
+
$stdin.getc
|
359
|
+
end
|
360
|
+
|
361
|
+
def show_help
|
362
|
+
clear_screen
|
363
|
+
puts "š„ļø EnhanceSwarm Dashboard Help".colorize(:cyan)
|
364
|
+
puts "ā" * 40
|
365
|
+
puts
|
366
|
+
puts "Controls:".colorize(:blue)
|
367
|
+
puts " q - Quit dashboard"
|
368
|
+
puts " r - Force refresh"
|
369
|
+
puts " p - Pause/resume"
|
370
|
+
puts " c - Clear screen"
|
371
|
+
puts " s - Save snapshot"
|
372
|
+
puts " d - Detailed view"
|
373
|
+
puts " h - Show this help"
|
374
|
+
puts
|
375
|
+
puts "Agent Status Icons:".colorize(:blue)
|
376
|
+
puts " š¢ Active - Agent is working"
|
377
|
+
puts " ā
Done - Agent completed successfully"
|
378
|
+
puts " ā Failed - Agent encountered an error"
|
379
|
+
puts " ā ļø Stuck - Agent appears stuck"
|
380
|
+
puts " š Starting - Agent is initializing"
|
381
|
+
puts
|
382
|
+
puts "Press any key to return...".colorize(:light_black)
|
383
|
+
$stdin.getc
|
384
|
+
end
|
385
|
+
|
386
|
+
def save_dashboard_snapshot
|
387
|
+
timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
|
388
|
+
filename = ".enhance_swarm/dashboard_snapshot_#{timestamp}.json"
|
389
|
+
|
390
|
+
snapshot = {
|
391
|
+
timestamp: Time.now.iso8601,
|
392
|
+
agents: @agents,
|
393
|
+
coordination: @coordination_status,
|
394
|
+
system_info: get_system_snapshot
|
395
|
+
}
|
396
|
+
|
397
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
398
|
+
File.write(filename, JSON.pretty_generate(snapshot))
|
399
|
+
|
400
|
+
flash_message("š¾ Snapshot saved: #{filename}")
|
401
|
+
end
|
402
|
+
|
403
|
+
def show_detailed_view
|
404
|
+
clear_screen
|
405
|
+
puts "š Detailed Agent View".colorize(:cyan)
|
406
|
+
puts "ā" * 50
|
407
|
+
|
408
|
+
@agents.each do |id, agent|
|
409
|
+
puts "\nš¤ #{agent[:role] || 'Unknown'} (#{id})".colorize(:blue)
|
410
|
+
puts " Status: #{format_agent_status(agent)}"
|
411
|
+
puts " PID: #{agent[:pid] || 'N/A'}"
|
412
|
+
puts " Task: #{agent[:current_task] || 'N/A'}"
|
413
|
+
puts " Progress: #{agent[:progress_percentage] || 0}%"
|
414
|
+
puts " Duration: #{format_duration(agent)}"
|
415
|
+
puts " Memory: #{agent[:memory_mb] || 'N/A'}MB" if agent[:memory_mb]
|
416
|
+
puts " Output: #{agent[:output_path] || 'N/A'}" if agent[:output_path]
|
417
|
+
end
|
418
|
+
|
419
|
+
puts "\nPress any key to return...".colorize(:light_black)
|
420
|
+
$stdin.getc
|
421
|
+
end
|
422
|
+
|
423
|
+
def flash_message(message)
|
424
|
+
# Save current position and show message
|
425
|
+
print "\e[s" # Save cursor position
|
426
|
+
height = @terminal_size[:height] || 24
|
427
|
+
print "\e[#{height - 2};1H" # Move to bottom
|
428
|
+
print message.colorize(:green)
|
429
|
+
sleep(2)
|
430
|
+
print "\e[u" # Restore cursor position
|
431
|
+
end
|
432
|
+
|
433
|
+
def input_available?
|
434
|
+
# Check if we're in an interactive terminal first
|
435
|
+
return false unless $stdin.tty?
|
436
|
+
|
437
|
+
# Non-blocking input check
|
438
|
+
ready = IO.select([$stdin], nil, nil, 0)
|
439
|
+
ready && ready[0].include?($stdin)
|
440
|
+
rescue StandardError
|
441
|
+
false
|
442
|
+
end
|
443
|
+
|
444
|
+
def get_terminal_size
|
445
|
+
begin
|
446
|
+
# Check if we're in an interactive terminal
|
447
|
+
return { width: 80, height: 24 } unless $stdin.tty?
|
448
|
+
|
449
|
+
stty_output = `stty size 2>/dev/null`.strip
|
450
|
+
return { width: 80, height: 24 } if stty_output.empty?
|
451
|
+
|
452
|
+
rows, cols = stty_output.split.map(&:to_i)
|
453
|
+
return { width: 80, height: 24 } if rows == 0 || cols == 0
|
454
|
+
|
455
|
+
{ width: cols, height: rows }
|
456
|
+
rescue StandardError
|
457
|
+
{ width: 80, height: 24 }
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
def get_memory_info
|
462
|
+
begin
|
463
|
+
if RUBY_PLATFORM.include?('darwin') # macOS
|
464
|
+
vm_stat = `vm_stat`
|
465
|
+
page_size = 4096
|
466
|
+
|
467
|
+
pages_free = vm_stat[/Pages free:\s+(\d+)/, 1].to_i
|
468
|
+
pages_wired = vm_stat[/Pages wired down:\s+(\d+)/, 1].to_i
|
469
|
+
pages_active = vm_stat[/Pages active:\s+(\d+)/, 1].to_i
|
470
|
+
pages_inactive = vm_stat[/Pages inactive:\s+(\d+)/, 1].to_i
|
471
|
+
|
472
|
+
total_pages = pages_free + pages_wired + pages_active + pages_inactive
|
473
|
+
used_pages = total_pages - pages_free
|
474
|
+
|
475
|
+
total_gb = (total_pages * page_size / 1024.0 / 1024.0 / 1024.0).round(1)
|
476
|
+
used_gb = (used_pages * page_size / 1024.0 / 1024.0 / 1024.0).round(1)
|
477
|
+
used_percent = ((used_pages.to_f / total_pages) * 100).round
|
478
|
+
|
479
|
+
{ total_gb: total_gb, used_gb: used_gb, used_percent: used_percent }
|
480
|
+
else
|
481
|
+
# Default fallback
|
482
|
+
{ total_gb: 8.0, used_gb: 4.0, used_percent: 50 }
|
483
|
+
end
|
484
|
+
rescue
|
485
|
+
{ total_gb: 8.0, used_gb: 4.0, used_percent: 50 }
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
def get_system_snapshot
|
490
|
+
{
|
491
|
+
memory: get_memory_info,
|
492
|
+
terminal_size: @terminal_size,
|
493
|
+
ruby_version: RUBY_VERSION,
|
494
|
+
platform: RUBY_PLATFORM,
|
495
|
+
timestamp: Time.now.iso8601
|
496
|
+
}
|
497
|
+
end
|
498
|
+
|
499
|
+
def time_ago(time)
|
500
|
+
seconds = Time.now - time
|
501
|
+
|
502
|
+
if seconds < 60
|
503
|
+
"#{seconds.round}s"
|
504
|
+
elsif seconds < 3600
|
505
|
+
"#{(seconds / 60).round}m"
|
506
|
+
else
|
507
|
+
"#{(seconds / 3600).round}h"
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
def format_time_duration(seconds)
|
512
|
+
if seconds < 60
|
513
|
+
"#{seconds.round}s"
|
514
|
+
elsif seconds < 3600
|
515
|
+
minutes = seconds / 60
|
516
|
+
"#{minutes.round}m"
|
517
|
+
else
|
518
|
+
hours = seconds / 3600
|
519
|
+
minutes = (seconds % 3600) / 60
|
520
|
+
"#{hours.round}h#{minutes.round}m"
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
# Class methods for singleton access
|
525
|
+
class << self
|
526
|
+
def instance
|
527
|
+
@instance ||= new
|
528
|
+
end
|
529
|
+
|
530
|
+
def start_dashboard(*args)
|
531
|
+
instance.start_dashboard(*args)
|
532
|
+
end
|
533
|
+
|
534
|
+
def stop_dashboard
|
535
|
+
instance.stop_dashboard
|
536
|
+
end
|
537
|
+
|
538
|
+
def update_agent(*args)
|
539
|
+
instance.update_agent(*args)
|
540
|
+
end
|
541
|
+
|
542
|
+
def update_coordination(*args)
|
543
|
+
instance.update_coordination(*args)
|
544
|
+
end
|
545
|
+
|
546
|
+
def add_agent(*args)
|
547
|
+
instance.add_agent(*args)
|
548
|
+
end
|
549
|
+
|
550
|
+
def remove_agent(*args)
|
551
|
+
instance.remove_agent(*args)
|
552
|
+
end
|
553
|
+
end
|
554
|
+
end
|
555
|
+
end
|