swarm_memory 2.1.5 → 2.1.6

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 (182) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_memory/version.rb +1 -1
  3. metadata +5 -184
  4. data/lib/claude_swarm/base_executor.rb +0 -133
  5. data/lib/claude_swarm/claude_code_executor.rb +0 -349
  6. data/lib/claude_swarm/claude_mcp_server.rb +0 -78
  7. data/lib/claude_swarm/cli.rb +0 -697
  8. data/lib/claude_swarm/commands/ps.rb +0 -215
  9. data/lib/claude_swarm/commands/show.rb +0 -139
  10. data/lib/claude_swarm/configuration.rb +0 -373
  11. data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
  12. data/lib/claude_swarm/json_handler.rb +0 -91
  13. data/lib/claude_swarm/mcp_generator.rb +0 -230
  14. data/lib/claude_swarm/openai/chat_completion.rb +0 -256
  15. data/lib/claude_swarm/openai/executor.rb +0 -256
  16. data/lib/claude_swarm/openai/responses.rb +0 -319
  17. data/lib/claude_swarm/orchestrator.rb +0 -878
  18. data/lib/claude_swarm/process_tracker.rb +0 -78
  19. data/lib/claude_swarm/session_cost_calculator.rb +0 -209
  20. data/lib/claude_swarm/session_path.rb +0 -42
  21. data/lib/claude_swarm/settings_generator.rb +0 -77
  22. data/lib/claude_swarm/system_utils.rb +0 -46
  23. data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
  24. data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
  25. data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
  26. data/lib/claude_swarm/tools/task_tool.rb +0 -63
  27. data/lib/claude_swarm/version.rb +0 -5
  28. data/lib/claude_swarm/worktree_manager.rb +0 -475
  29. data/lib/claude_swarm/yaml_loader.rb +0 -22
  30. data/lib/claude_swarm.rb +0 -67
  31. data/lib/swarm_cli/cli.rb +0 -201
  32. data/lib/swarm_cli/command_registry.rb +0 -61
  33. data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
  34. data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
  35. data/lib/swarm_cli/commands/migrate.rb +0 -55
  36. data/lib/swarm_cli/commands/run.rb +0 -173
  37. data/lib/swarm_cli/config_loader.rb +0 -98
  38. data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
  39. data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
  40. data/lib/swarm_cli/interactive_repl.rb +0 -924
  41. data/lib/swarm_cli/mcp_serve_options.rb +0 -44
  42. data/lib/swarm_cli/mcp_tools_options.rb +0 -59
  43. data/lib/swarm_cli/migrate_options.rb +0 -54
  44. data/lib/swarm_cli/migrator.rb +0 -132
  45. data/lib/swarm_cli/options.rb +0 -151
  46. data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
  47. data/lib/swarm_cli/ui/components/content_block.rb +0 -120
  48. data/lib/swarm_cli/ui/components/divider.rb +0 -57
  49. data/lib/swarm_cli/ui/components/panel.rb +0 -62
  50. data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
  51. data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
  52. data/lib/swarm_cli/ui/formatters/number.rb +0 -58
  53. data/lib/swarm_cli/ui/formatters/text.rb +0 -77
  54. data/lib/swarm_cli/ui/formatters/time.rb +0 -73
  55. data/lib/swarm_cli/ui/icons.rb +0 -36
  56. data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
  57. data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
  58. data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
  59. data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
  60. data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
  61. data/lib/swarm_cli/version.rb +0 -5
  62. data/lib/swarm_cli.rb +0 -46
  63. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
  64. data/lib/swarm_sdk/agent/builder.rb +0 -552
  65. data/lib/swarm_sdk/agent/chat.rb +0 -774
  66. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
  67. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  68. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  69. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -78
  70. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
  71. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  72. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  73. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -136
  74. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  75. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
  76. data/lib/swarm_sdk/agent/context.rb +0 -116
  77. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  78. data/lib/swarm_sdk/agent/definition.rb +0 -477
  79. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
  80. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  81. data/lib/swarm_sdk/builders/base_builder.rb +0 -409
  82. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  83. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  84. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  85. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  86. data/lib/swarm_sdk/configuration/parser.rb +0 -353
  87. data/lib/swarm_sdk/configuration/translator.rb +0 -255
  88. data/lib/swarm_sdk/configuration.rb +0 -135
  89. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  90. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
  91. data/lib/swarm_sdk/context_compactor.rb +0 -335
  92. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  93. data/lib/swarm_sdk/context_management/context.rb +0 -328
  94. data/lib/swarm_sdk/defaults.rb +0 -196
  95. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  96. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  97. data/lib/swarm_sdk/hooks/context.rb +0 -197
  98. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  99. data/lib/swarm_sdk/hooks/error.rb +0 -29
  100. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  101. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  102. data/lib/swarm_sdk/hooks/result.rb +0 -150
  103. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -255
  104. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  105. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  106. data/lib/swarm_sdk/log_collector.rb +0 -227
  107. data/lib/swarm_sdk/log_stream.rb +0 -127
  108. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  109. data/lib/swarm_sdk/model_aliases.json +0 -8
  110. data/lib/swarm_sdk/models.json +0 -1
  111. data/lib/swarm_sdk/models.rb +0 -120
  112. data/lib/swarm_sdk/node_context.rb +0 -245
  113. data/lib/swarm_sdk/observer/builder.rb +0 -81
  114. data/lib/swarm_sdk/observer/config.rb +0 -45
  115. data/lib/swarm_sdk/observer/manager.rb +0 -236
  116. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  117. data/lib/swarm_sdk/permissions/config.rb +0 -239
  118. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  119. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  120. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  121. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  122. data/lib/swarm_sdk/plugin.rb +0 -309
  123. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  124. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  125. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  126. data/lib/swarm_sdk/restore_result.rb +0 -65
  127. data/lib/swarm_sdk/result.rb +0 -123
  128. data/lib/swarm_sdk/snapshot.rb +0 -156
  129. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  130. data/lib/swarm_sdk/state_restorer.rb +0 -476
  131. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  132. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -683
  133. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
  134. data/lib/swarm_sdk/swarm/builder.rb +0 -249
  135. data/lib/swarm_sdk/swarm/executor.rb +0 -213
  136. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
  137. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
  138. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
  139. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  140. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
  141. data/lib/swarm_sdk/swarm.rb +0 -717
  142. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  143. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  144. data/lib/swarm_sdk/tools/bash.rb +0 -282
  145. data/lib/swarm_sdk/tools/clock.rb +0 -44
  146. data/lib/swarm_sdk/tools/delegate.rb +0 -267
  147. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  148. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  149. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  150. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  151. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  152. data/lib/swarm_sdk/tools/edit.rb +0 -145
  153. data/lib/swarm_sdk/tools/glob.rb +0 -166
  154. data/lib/swarm_sdk/tools/grep.rb +0 -235
  155. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  156. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -163
  157. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  158. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  159. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  160. data/lib/swarm_sdk/tools/read.rb +0 -261
  161. data/lib/swarm_sdk/tools/registry.rb +0 -205
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  165. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  166. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -272
  167. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  168. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  169. data/lib/swarm_sdk/tools/think.rb +0 -98
  170. data/lib/swarm_sdk/tools/todo_write.rb +0 -235
  171. data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
  172. data/lib/swarm_sdk/tools/write.rb +0 -112
  173. data/lib/swarm_sdk/utils.rb +0 -68
  174. data/lib/swarm_sdk/validation_result.rb +0 -33
  175. data/lib/swarm_sdk/version.rb +0 -5
  176. data/lib/swarm_sdk/workflow/agent_config.rb +0 -79
  177. data/lib/swarm_sdk/workflow/builder.rb +0 -143
  178. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  179. data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
  180. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
  181. data/lib/swarm_sdk/workflow.rb +0 -554
  182. data/lib/swarm_sdk.rb +0 -524
@@ -1,215 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ClaudeSwarm
4
- module Commands
5
- class Ps
6
- def execute
7
- run_dir = ClaudeSwarm.joined_run_dir
8
- unless Dir.exist?(run_dir)
9
- puts "No active sessions"
10
- return
11
- end
12
-
13
- # Read all symlinks in run directory and process them
14
- sessions = Dir.glob("#{run_dir}/*").filter_map do |symlink|
15
- process_symlink(symlink)
16
- end
17
-
18
- if sessions.empty?
19
- puts "No active sessions"
20
- return
21
- end
22
-
23
- # Check if any session is missing main instance costs
24
- any_missing_main = sessions.any? { |s| !s[:main_has_cost] }
25
-
26
- # Column widths
27
- col_session = 15
28
- col_swarm = 25
29
- col_cost = 12
30
- col_uptime = 10
31
-
32
- # Display header with proper spacing
33
- header = "#{
34
- "SESSION_ID".ljust(col_session)
35
- } #{
36
- "SWARM_NAME".ljust(col_swarm)
37
- } #{
38
- "TOTAL_COST".ljust(col_cost)
39
- } #{
40
- "UPTIME".ljust(col_uptime)
41
- } DIRECTORY"
42
-
43
- # Only show warning if any session is missing main instance costs
44
- if any_missing_main
45
- puts "\n⚠️ \e[3mTotal cost does not include the cost of the main instance for some sessions\e[0m\n\n"
46
- else
47
- puts
48
- end
49
-
50
- puts header
51
- puts "-" * header.length
52
-
53
- # Display sessions sorted by start time (newest first)
54
- sessions.sort_by { |s| s[:start_time] }.reverse.each do |session|
55
- cost_str = format("$%.4f", session[:cost])
56
- # Add asterisk if this session is missing main instance cost
57
- cost_str += "*" unless session[:main_has_cost]
58
-
59
- puts "#{
60
- session[:id].ljust(col_session)
61
- } #{
62
- truncate(session[:name], col_swarm).ljust(col_swarm)
63
- } #{
64
- cost_str.ljust(col_cost)
65
- } #{
66
- session[:uptime].ljust(col_uptime)
67
- } #{session[:directory]}"
68
- end
69
- end
70
-
71
- private
72
-
73
- def process_symlink(symlink)
74
- session_dir = File.readlink(symlink)
75
- session_id = File.basename(session_dir)
76
- # Skip if target doesn't exist (stale symlink)
77
- return unless Dir.exist?(session_dir)
78
-
79
- parse_session_info(session_id, session_dir)
80
- rescue Errno::EINVAL
81
- # Not a symlink, skip it
82
- nil
83
- rescue StandardError => e
84
- # Try to get session_id if we have session_dir
85
- warn("⚠️ Skipping session #{session_id}: #{e.message}")
86
- nil
87
- end
88
-
89
- def parse_session_info(session_id, session_dir)
90
- # Load config for swarm name and main directory
91
- config_file = File.join(session_dir, "config.yml")
92
- return unless File.exist?(config_file)
93
-
94
- config = YamlLoader.load_config_file(config_file)
95
- swarm_name = config.dig("swarm", "name") || "Unknown"
96
- main_instance = config.dig("swarm", "main")
97
-
98
- # Get base directory from session metadata or root_directory file
99
- root_dir_file = File.join(session_dir, "root_directory")
100
- base_dir = File.exist?(root_dir_file) ? File.read(root_dir_file).strip : Dir.pwd
101
-
102
- # Get all directories - handle both string and array formats
103
- dir_config = config.dig("swarm", "instances", main_instance, "directory")
104
- directories = if dir_config.is_a?(Array)
105
- dir_config
106
- else
107
- [dir_config || "."]
108
- end
109
-
110
- # Expand paths relative to the base directory
111
- expanded_directories = directories.map do |dir|
112
- File.expand_path(dir, base_dir)
113
- end
114
-
115
- # Check for worktree information in session metadata
116
- expanded_directories = apply_worktree_paths(expanded_directories, session_dir)
117
-
118
- directories_str = expanded_directories.join(", ")
119
-
120
- # Calculate total cost from JSON log
121
- log_file = File.join(session_dir, "session.log.json")
122
- cost_result = SessionCostCalculator.calculate_total_cost(log_file)
123
- total_cost = cost_result[:total_cost]
124
-
125
- # Check if main instance has cost data
126
- instances_with_cost = cost_result[:instances_with_cost]
127
- main_has_cost = main_instance && instances_with_cost.include?(main_instance)
128
-
129
- # Get uptime from session metadata or fallback to directory creation time
130
- start_time = get_start_time(session_dir)
131
- uptime = format_duration(Time.now - start_time)
132
-
133
- {
134
- id: session_id,
135
- name: swarm_name,
136
- cost: total_cost,
137
- main_has_cost: main_has_cost,
138
- uptime: uptime,
139
- directory: directories_str,
140
- start_time: start_time,
141
- }
142
- end
143
-
144
- def get_start_time(session_dir)
145
- # Try to get from session metadata first
146
- metadata_file = File.join(session_dir, "session_metadata.json")
147
- metadata = JsonHandler.parse_file(metadata_file)
148
-
149
- if metadata && metadata["start_time"]
150
- return Time.parse(metadata["start_time"])
151
- end
152
-
153
- # Fallback to directory creation time
154
- File.stat(session_dir).ctime
155
- rescue StandardError
156
- # If anything fails, use directory creation time
157
- File.stat(session_dir).ctime
158
- end
159
-
160
- def format_duration(seconds)
161
- if seconds < 60
162
- "#{seconds.to_i}s"
163
- elsif seconds < 3600
164
- "#{(seconds / 60).to_i}m"
165
- elsif seconds < 86_400
166
- "#{(seconds / 3600).to_i}h"
167
- else
168
- "#{(seconds / 86_400).to_i}d"
169
- end
170
- end
171
-
172
- def truncate(str, length)
173
- str.length > length ? "#{str[0...length - 2]}.." : str
174
- end
175
-
176
- def apply_worktree_paths(directories, session_dir)
177
- session_metadata_file = File.join(session_dir, "session_metadata.json")
178
- return directories unless File.exist?(session_metadata_file)
179
-
180
- metadata = JsonHandler.parse_file!(session_metadata_file)
181
- worktree_info = metadata["worktree"]
182
- return directories unless worktree_info && worktree_info["enabled"]
183
-
184
- # Get the created worktree paths
185
- created_paths = worktree_info["created_paths"] || {}
186
-
187
- # For each directory, find the appropriate worktree path
188
- directories.map do |dir|
189
- # Find if this directory has a worktree created
190
- repo_root = find_git_root(dir)
191
- next dir unless repo_root
192
-
193
- # Look for a worktree with this repo root
194
- worktree_key = created_paths.keys.find { |key| key.start_with?("#{repo_root}:") }
195
- worktree_key ? created_paths[worktree_key] : dir
196
- end
197
- end
198
-
199
- def worktree_path_for(dir, worktree_name)
200
- git_root = find_git_root(dir)
201
- git_root ? File.join(git_root, ".worktrees", worktree_name) : dir
202
- end
203
-
204
- def find_git_root(dir)
205
- current = File.expand_path(dir)
206
- while current != "/"
207
- return current if File.exist?(File.join(current, ".git"))
208
-
209
- current = File.dirname(current)
210
- end
211
- nil
212
- end
213
- end
214
- end
215
- end
@@ -1,139 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ClaudeSwarm
4
- module Commands
5
- class Show
6
- def execute(session_id)
7
- session_path = find_session_path(session_id)
8
- unless session_path
9
- puts "Session not found: #{session_id}"
10
- exit(1)
11
- end
12
-
13
- # Load config to get main instance name
14
- config = YamlLoader.load_config_file(File.join(session_path, "config.yml"))
15
- main_instance_name = config.dig("swarm", "main")
16
-
17
- # Parse all events to build instance data
18
- log_file = File.join(session_path, "session.log.json")
19
- instances = SessionCostCalculator.parse_instance_hierarchy(log_file)
20
-
21
- # Calculate total cost (excluding main if not available)
22
- total_cost = instances.values.sum { |i| i[:cost] }
23
- cost_display = if instances[main_instance_name] && instances[main_instance_name][:has_cost_data]
24
- format("$%.4f", total_cost)
25
- else
26
- "#{format("$%.4f", total_cost)} (excluding main instance)"
27
- end
28
-
29
- # Display session info
30
- puts "Session: #{session_id}"
31
- puts "Session Path: #{session_path}"
32
- puts "Swarm: #{config.dig("swarm", "name")}"
33
-
34
- # Display runtime if available
35
- runtime_info = get_runtime_info(session_path)
36
- puts "Runtime: #{runtime_info}" if runtime_info
37
-
38
- puts "Total Cost: #{cost_display}"
39
-
40
- # Try to read root directory
41
- root_dir_file = File.join(session_path, "root_directory")
42
- puts "Root Directory: #{File.read(root_dir_file).strip}" if File.exist?(root_dir_file)
43
-
44
- puts
45
- puts "Instance Hierarchy:"
46
- puts "-" * 50
47
-
48
- # Find root instances
49
- roots = instances.values.select { |i| i[:called_by].empty? }
50
- roots.each do |instance|
51
- display_instance_tree(instance, instances, 0, main_instance_name)
52
- end
53
-
54
- # Add note about interactive main instance
55
- return if instances[main_instance_name]&.dig(:has_cost_data)
56
-
57
- puts
58
- puts "Note: Main instance (#{main_instance_name}) cost is not tracked in interactive mode."
59
- puts " View costs directly in the Claude interface."
60
- end
61
-
62
- private
63
-
64
- def find_session_path(session_id)
65
- # First check the run directory
66
- run_symlink = ClaudeSwarm.joined_run_dir(session_id)
67
- if File.symlink?(run_symlink)
68
- target = File.readlink(run_symlink)
69
- return target if Dir.exist?(target)
70
- end
71
-
72
- # Fall back to searching all sessions
73
- Dir.glob(ClaudeSwarm.joined_sessions_dir("*", "*")).find do |path|
74
- File.basename(path) == session_id
75
- end
76
- end
77
-
78
- def get_runtime_info(session_path)
79
- metadata_file = File.join(session_path, "session_metadata.json")
80
- metadata = JsonHandler.parse_file(metadata_file)
81
- return unless metadata
82
-
83
- if metadata["duration_seconds"]
84
- # Session has completed
85
- format_duration(metadata["duration_seconds"])
86
- elsif metadata["start_time"]
87
- # Session is still running or was interrupted
88
- start_time = Time.parse(metadata["start_time"])
89
- duration = (Time.now - start_time).to_i
90
- "#{format_duration(duration)} (active)"
91
- end
92
- rescue StandardError
93
- nil
94
- end
95
-
96
- def format_duration(seconds)
97
- hours = seconds / 3600
98
- minutes = (seconds % 3600) / 60
99
- secs = seconds % 60
100
-
101
- parts = []
102
- parts << "#{hours}h" if hours.positive?
103
- parts << "#{minutes}m" if minutes.positive?
104
- parts << "#{secs}s"
105
-
106
- parts.join(" ")
107
- end
108
-
109
- def display_instance_tree(instance, all_instances, level, main_instance_name)
110
- indent = " " * level
111
- prefix = level.zero? ? "├─" : "└─"
112
-
113
- # Display instance name with special marker for main
114
- instance_display = instance[:name]
115
- instance_display += " [main]" if instance[:name] == main_instance_name
116
-
117
- puts "#{indent}#{prefix} #{instance_display} (#{instance[:id]})"
118
-
119
- # Display cost - show n/a for main instance without cost data
120
- cost_display = if instance[:name] == main_instance_name && !instance[:has_cost_data]
121
- "n/a (interactive)"
122
- else
123
- format("$%.4f", instance[:cost])
124
- end
125
-
126
- puts "#{indent} Cost: #{cost_display} | Calls: #{instance[:calls]}"
127
-
128
- # Display children
129
- children = instance[:calls_to].map { |name| all_instances[name] }.compact
130
- children.each do |child|
131
- # Don't recurse if we've already shown this instance (avoid cycles)
132
- next if level.positive? && child[:called_by].size > 1
133
-
134
- display_instance_tree(child, all_instances, level + 1, main_instance_name)
135
- end
136
- end
137
- end
138
- end
139
- end