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,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmCLI
4
- module UI
5
- module Components
6
- # Renders highlighted panels for warnings, info, errors
7
- # Uses top/bottom borders only (no sides per design constraint)
8
- class Panel
9
- TYPE_CONFIGS = {
10
- warning: { color: :yellow, icon: UI::Icons::WARNING },
11
- error: { color: :red, icon: UI::Icons::ERROR },
12
- info: { color: :cyan, icon: UI::Icons::INFO },
13
- success: { color: :green, icon: UI::Icons::SUCCESS },
14
- }.freeze
15
-
16
- def initialize(pastel:, terminal_width: 80)
17
- @pastel = pastel
18
- @terminal_width = terminal_width
19
- end
20
-
21
- # Render panel with top/bottom borders
22
- #
23
- # ⚠️ CONTEXT WARNING
24
- # Context usage: 81.4% (threshold: 80%)
25
- # Tokens remaining: 74,523
26
- #
27
- def render(type:, title:, lines:, indent: 0)
28
- config = TYPE_CONFIGS[type] || TYPE_CONFIGS[:info]
29
- prefix = " " * indent
30
-
31
- output = []
32
-
33
- # Title line with icon
34
- icon = config[:icon]
35
- colored_title = @pastel.public_send(config[:color], "#{icon} #{title}")
36
- output << "#{prefix}#{colored_title}"
37
-
38
- # Content lines
39
- lines.each do |line|
40
- output << "#{prefix} #{line}"
41
- end
42
-
43
- output << "" # Blank line after panel
44
-
45
- output.join("\n")
46
- end
47
-
48
- # Render compact panel (single line)
49
- # ⚠️ Context approaching limit (81.4%)
50
- def render_compact(type:, message:, indent: 0)
51
- config = TYPE_CONFIGS[type] || TYPE_CONFIGS[:info]
52
- prefix = " " * indent
53
-
54
- icon = config[:icon]
55
- colored_msg = @pastel.public_send(config[:color], "#{icon} #{message}")
56
-
57
- "#{prefix}#{colored_msg}"
58
- end
59
- end
60
- end
61
- end
62
- end
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmCLI
4
- module UI
5
- module Components
6
- # Renders usage statistics (tokens, cost, context percentage)
7
- class UsageStats
8
- def initialize(pastel:)
9
- @pastel = pastel
10
- end
11
-
12
- # Render usage line with all available metrics
13
- # 5,922 tokens │ $0.0016 │ 1.5% used, 394,078 remaining
14
- def render(tokens:, cost:, context_pct: nil, remaining: nil, cumulative: nil)
15
- parts = []
16
-
17
- # Token count (always shown)
18
- parts << "#{Formatters::Number.format(tokens)} tokens"
19
-
20
- # Cost (always shown if > 0)
21
- parts << Formatters::Cost.format(cost, pastel: @pastel) if cost > 0
22
-
23
- # Context tracking (if available)
24
- if context_pct
25
- colored_pct = color_context_percentage(context_pct)
26
-
27
- parts << if remaining
28
- "#{colored_pct} used, #{Formatters::Number.compact(remaining)} remaining"
29
- else
30
- "#{colored_pct} used"
31
- end
32
- elsif cumulative
33
- # Model doesn't have context limit, show cumulative
34
- parts << "#{Formatters::Number.compact(cumulative)} cumulative"
35
- end
36
-
37
- @pastel.dim(parts.join(" #{@pastel.dim("│")} "))
38
- end
39
-
40
- # Render compact stats for prompt display
41
- # 15.2K tokens • $0.045 • 3.8% context
42
- def render_compact(tokens:, cost:, context_pct: nil)
43
- parts = []
44
-
45
- parts << "#{Formatters::Number.compact(tokens)} tokens" if tokens > 0
46
- parts << Formatters::Cost.format_plain(cost) if cost > 0
47
- parts << "#{context_pct} context" if context_pct
48
-
49
- parts.join(" • ")
50
- end
51
-
52
- private
53
-
54
- def color_context_percentage(percentage_string)
55
- percentage = percentage_string.to_s.gsub("%", "").to_f
56
-
57
- color = if percentage < 50
58
- :green
59
- elsif percentage < 80
60
- :yellow
61
- else
62
- :red
63
- end
64
-
65
- @pastel.public_send(color, percentage_string)
66
- end
67
- end
68
- end
69
- end
70
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmCLI
4
- module UI
5
- module Formatters
6
- # Cost formatting with color coding
7
- class Cost
8
- class << self
9
- # Format cost with appropriate precision and color
10
- # Small costs: green, $0.001234
11
- # Medium costs: yellow, $0.1234
12
- # Large costs: red, $12.34
13
- def format(cost, pastel:)
14
- return pastel.dim("$0.0000") if cost.nil? || cost.zero?
15
-
16
- formatted = if cost < 0.01
17
- Kernel.format("%.6f", cost)
18
- elsif cost < 1.0
19
- Kernel.format("%.4f", cost)
20
- else
21
- Kernel.format("%.2f", cost)
22
- end
23
-
24
- if cost < 0.01
25
- pastel.green("$#{formatted}")
26
- elsif cost < 1.0
27
- pastel.yellow("$#{formatted}")
28
- else
29
- pastel.red("$#{formatted}")
30
- end
31
- end
32
-
33
- # Format cost without color (for plain text)
34
- def format_plain(cost)
35
- return "$0.0000" if cost.nil? || cost.zero?
36
-
37
- if cost < 0.01
38
- Kernel.format("$%.6f", cost)
39
- elsif cost < 1.0
40
- Kernel.format("$%.4f", cost)
41
- else
42
- Kernel.format("$%.2f", cost)
43
- end
44
- end
45
- end
46
- end
47
- end
48
- end
49
- end
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmCLI
4
- module UI
5
- module Formatters
6
- # Number formatting utilities for terminal display
7
- class Number
8
- class << self
9
- # Format number with thousand separators
10
- # 5922 → "5,922"
11
- # 1500000 → "1,500,000"
12
- def format(num)
13
- return "0" if num.nil? || num.zero?
14
-
15
- num.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
16
- end
17
-
18
- # Format number with compact units (K, M, B)
19
- # 5922 → "5.9K"
20
- # 1500000 → "1.5M"
21
- # 1500000000 → "1.5B"
22
- def compact(num)
23
- return "0" if num.nil? || num.zero?
24
-
25
- case num
26
- when 0...1_000
27
- num.to_s
28
- when 1_000...1_000_000
29
- "#{(num / 1_000.0).round(1)}K"
30
- when 1_000_000...1_000_000_000
31
- "#{(num / 1_000_000.0).round(1)}M"
32
- else
33
- "#{(num / 1_000_000_000.0).round(1)}B"
34
- end
35
- end
36
-
37
- # Format bytes with units (KB, MB, GB)
38
- # 1024 → "1.0 KB"
39
- # 1500000 → "1.4 MB"
40
- def bytes(num)
41
- return "0 B" if num.nil? || num.zero?
42
-
43
- case num
44
- when 0...1024
45
- "#{num} B"
46
- when 1024...1024**2
47
- "#{(num / 1024.0).round(1)} KB"
48
- when 1024**2...1024**3
49
- "#{(num / 1024.0**2).round(1)} MB"
50
- else
51
- "#{(num / 1024.0**3).round(1)} GB"
52
- end
53
- end
54
- end
55
- end
56
- end
57
- end
58
- end
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmCLI
4
- module UI
5
- module Formatters
6
- # Text manipulation utilities for clean display
7
- class Text
8
- class << self
9
- # Strip <system-reminder> tags from content
10
- def strip_system_reminders(text)
11
- return "" if text.nil?
12
-
13
- text.gsub(%r{<system-reminder>.*?</system-reminder>}m, "").strip
14
- end
15
-
16
- # Truncate text to specified character/line limits
17
- # Returns [display_text, truncation_message]
18
- def truncate(text, chars: nil, lines: nil)
19
- return [text, nil] if text.nil? || text.empty?
20
-
21
- text_lines = text.split("\n")
22
- truncated = false
23
- truncation_parts = []
24
-
25
- # Apply line limit
26
- if lines && text_lines.length > lines
27
- text_lines = text_lines.first(lines)
28
- hidden_lines = text.split("\n").length - lines
29
- truncation_parts << "#{hidden_lines} more lines"
30
- truncated = true
31
- end
32
-
33
- result_text = text_lines.join("\n")
34
-
35
- # Apply character limit
36
- if chars && result_text.length > chars
37
- result_text = result_text[0...chars]
38
- hidden_chars = text.length - chars
39
- truncation_parts << "#{hidden_chars} more chars"
40
- truncated = true
41
- end
42
-
43
- truncation_msg = truncated ? "... (#{truncation_parts.join(", ")})" : nil
44
-
45
- [result_text, truncation_msg]
46
- end
47
-
48
- # Wrap text to specified width
49
- def wrap(text, width:)
50
- return "" if text.nil? || text.empty?
51
-
52
- text.split("\n").flat_map do |line|
53
- wrap_line(line, width)
54
- end.join("\n")
55
- end
56
-
57
- # Indent all lines in text
58
- def indent(text, level: 0, char: " ")
59
- return "" if text.nil? || text.empty?
60
-
61
- prefix = char * level
62
- text.split("\n").map { |line| "#{prefix}#{line}" }.join("\n")
63
- end
64
-
65
- private
66
-
67
- # Word wrap a single line
68
- def wrap_line(line, width)
69
- return [line] if line.length <= width
70
-
71
- line.scan(/.{1,#{width}}(?:\s+|$)/).map(&:strip)
72
- end
73
- end
74
- end
75
- end
76
- end
77
- end
@@ -1,73 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmCLI
4
- module UI
5
- module Formatters
6
- # Time and duration formatting utilities
7
- class Time
8
- class << self
9
- # Format timestamp as [HH:MM:SS]
10
- # Time.now → "[12:34:56]"
11
- def timestamp(time)
12
- return "" if time.nil?
13
-
14
- case time
15
- when ::Time
16
- time.strftime("[%H:%M:%S]")
17
- when String
18
- parsed = ::Time.parse(time)
19
- parsed.strftime("[%H:%M:%S]")
20
- else
21
- ""
22
- end
23
- rescue StandardError
24
- ""
25
- end
26
-
27
- # Format duration in human-readable form
28
- # 0.5 → "500ms"
29
- # 2.3 → "2.3s"
30
- # 65 → "1m 5s"
31
- # 3665 → "1h 1m 5s"
32
- def duration(seconds)
33
- return "0ms" if seconds.nil? || seconds.zero?
34
-
35
- if seconds < 1
36
- "#{(seconds * 1000).round}ms"
37
- elsif seconds < 60
38
- "#{seconds.round(2)}s"
39
- elsif seconds < 3600
40
- minutes = (seconds / 60).floor
41
- secs = (seconds % 60).round
42
- "#{minutes}m #{secs}s"
43
- else
44
- hours = (seconds / 3600).floor
45
- minutes = ((seconds % 3600) / 60).floor
46
- secs = (seconds % 60).round
47
- "#{hours}h #{minutes}m #{secs}s"
48
- end
49
- end
50
-
51
- # Format relative time (future enhancement)
52
- # Time.now - 120 → "2 minutes ago"
53
- def relative(time)
54
- return "" if time.nil?
55
-
56
- seconds_ago = ::Time.now - time
57
-
58
- case seconds_ago
59
- when 0...60
60
- "#{seconds_ago.round}s ago"
61
- when 60...3600
62
- "#{(seconds_ago / 60).round}m ago"
63
- when 3600...86400
64
- "#{(seconds_ago / 3600).round}h ago"
65
- else
66
- "#{(seconds_ago / 86400).round}d ago"
67
- end
68
- end
69
- end
70
- end
71
- end
72
- end
73
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmCLI
4
- module UI
5
- # Icon definitions for terminal UI
6
- # Centralized so all components use the same icons
7
- module Icons
8
- # Event type icons
9
- THINKING = "💭"
10
- RESPONSE = "💬"
11
- SUCCESS = "✓"
12
- ERROR = "✗"
13
- INFO = "ℹ"
14
- WARNING = "⚠️"
15
-
16
- # Entity icons
17
- AGENT = "🤖"
18
- TOOL = "🔧"
19
- DELEGATE = "📨"
20
- RESULT = "📥"
21
- HOOK = "🪝"
22
-
23
- # Metric icons
24
- LLM = "🧠"
25
- TOKENS = "📊"
26
- COST = "💰"
27
- TIME = "⏱"
28
-
29
- # Visual elements
30
- SPARKLES = "✨"
31
- ARROW_RIGHT = "→"
32
- BULLET = "•"
33
- COMPRESS = "🗜️"
34
- end
35
- end
36
- end
@@ -1,188 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmCLI
4
- module UI
5
- module Renderers
6
- # High-level event rendering by composing lower-level components
7
- # Returns formatted strings for each event type
8
- class EventRenderer
9
- def initialize(pastel:, agent_badge:, depth_tracker:)
10
- @pastel = pastel
11
- @agent_badge = agent_badge
12
- @depth_tracker = depth_tracker
13
- @usage_stats = Components::UsageStats.new(pastel: pastel)
14
- @content_block = Components::ContentBlock.new(pastel: pastel)
15
- @panel = Components::Panel.new(pastel: pastel)
16
- end
17
-
18
- # Render agent thinking event
19
- # [12:34:56] 💭 architect (gpt-5-mini)
20
- def agent_thinking(agent:, model:, timestamp:)
21
- indent = @depth_tracker.indent(agent)
22
- time = Formatters::Time.timestamp(timestamp)
23
- agent_name = @agent_badge.render(agent, icon: UI::Icons::THINKING)
24
- model_info = @pastel.dim("(#{model})")
25
-
26
- "#{indent}#{@pastel.dim(time)} #{agent_name} #{model_info}"
27
- end
28
-
29
- # Render agent response event
30
- # [12:34:56] 💬 architect responded:
31
- def agent_response(agent:, timestamp:)
32
- indent = @depth_tracker.indent(agent)
33
- time = Formatters::Time.timestamp(timestamp)
34
- agent_name = @agent_badge.render(agent, icon: UI::Icons::RESPONSE)
35
-
36
- "#{indent}#{@pastel.dim(time)} #{agent_name} responded:"
37
- end
38
-
39
- # Render agent completion
40
- # ✓ architect completed
41
- def agent_completed(agent:)
42
- indent = @depth_tracker.indent(agent)
43
- agent_name = @agent_badge.render(agent)
44
-
45
- "#{indent}#{@pastel.green("#{UI::Icons::SUCCESS} #{agent_name} completed")}"
46
- end
47
-
48
- # Render tool call event
49
- # [12:34:56] architect 🔧 uses tool Read
50
- def tool_call(agent:, tool:, timestamp:)
51
- indent = @depth_tracker.indent(agent)
52
- time = Formatters::Time.timestamp(timestamp)
53
- agent_name = @agent_badge.render(agent)
54
- tool_name = @pastel.bold.blue(tool)
55
-
56
- "#{indent}#{@pastel.dim(time)} #{agent_name} #{@pastel.blue("#{UI::Icons::TOOL} uses tool")} #{tool_name}"
57
- end
58
-
59
- # Render tool result received
60
- # [12:34:56] 📥 Tool result received by architect
61
- def tool_result(agent:, timestamp:, tool: nil)
62
- indent = @depth_tracker.indent(agent)
63
- time = Formatters::Time.timestamp(timestamp)
64
-
65
- "#{indent}#{@pastel.dim(time)} #{@pastel.green("#{UI::Icons::RESULT} Tool result")} received by #{agent}"
66
- end
67
-
68
- # Render delegation event
69
- # [12:34:56] architect 📨 delegates to worker
70
- def delegation(from:, to:, timestamp:)
71
- indent = @depth_tracker.indent(from)
72
- time = Formatters::Time.timestamp(timestamp)
73
- from_name = @agent_badge.render(from)
74
- to_name = @agent_badge.render(to)
75
-
76
- "#{indent}#{@pastel.dim(time)} #{from_name} #{@pastel.yellow("#{UI::Icons::DELEGATE} delegates to")} #{to_name}"
77
- end
78
-
79
- # Render delegation result
80
- # [12:34:56] 📥 Delegation result from worker → architect
81
- def delegation_result(from:, to:, timestamp:)
82
- indent = @depth_tracker.indent(to)
83
- time = Formatters::Time.timestamp(timestamp)
84
- from_name = @agent_badge.render(from)
85
- to_name = @agent_badge.render(to)
86
-
87
- "#{indent}#{@pastel.dim(time)} #{@pastel.green("#{UI::Icons::RESULT} Delegation result")} from #{from_name} #{@pastel.dim("→")} #{to_name}"
88
- end
89
-
90
- # Render hook execution
91
- # [12:34:56] 🪝 Hook executed PreToolUse architect
92
- def hook_executed(hook_event:, agent:, timestamp:, success:, blocked:)
93
- indent = @depth_tracker.indent(agent)
94
- time = Formatters::Time.timestamp(timestamp)
95
- hook_display = @pastel.cyan(hook_event)
96
- agent_name = @agent_badge.render(agent)
97
-
98
- status = if blocked
99
- @pastel.red("BLOCKED")
100
- elsif success
101
- @pastel.green("executed")
102
- else
103
- @pastel.yellow("warning")
104
- end
105
-
106
- color = if blocked
107
- :red
108
- else
109
- (success ? :green : :yellow)
110
- end
111
- icon_colored = @pastel.public_send(color, UI::Icons::HOOK)
112
-
113
- "#{indent}#{@pastel.dim(time)} #{icon_colored} Hook #{status} #{hook_display} #{agent_name}"
114
- end
115
-
116
- # Render usage stats line
117
- # 5,922 tokens │ $0.0016 │ 1.5% used, 394,078 remaining
118
- def usage_stats(tokens:, cost:, context_pct: nil, remaining: nil, cumulative: nil, indent: 0)
119
- prefix = " " * indent
120
- stats = @usage_stats.render(
121
- tokens: tokens,
122
- cost: cost,
123
- context_pct: context_pct,
124
- remaining: remaining,
125
- cumulative: cumulative,
126
- )
127
-
128
- "#{prefix} #{stats}"
129
- end
130
-
131
- # Render tool list
132
- # Tools available: Read, Write, Bash
133
- def tools_available(tools, indent: 0)
134
- return "" if tools.nil? || tools.empty?
135
-
136
- prefix = " " * indent
137
- tools_list = tools.join(", ")
138
-
139
- "#{prefix} #{@pastel.dim("Tools available: #{tools_list}")}"
140
- end
141
-
142
- # Render delegation list
143
- # Can delegate to: frontend_dev, backend_dev
144
- def delegates_to(agents, indent: 0, color_cache:)
145
- return "" if agents.nil? || agents.empty?
146
-
147
- prefix = " " * indent
148
- agent_badge = Components::AgentBadge.new(pastel: @pastel, color_cache: color_cache)
149
- delegates_list = agent_badge.render_list(agents)
150
-
151
- "#{prefix} #{@pastel.dim("Can delegate to:")} #{delegates_list}"
152
- end
153
-
154
- # Render thinking text (italic, indented)
155
- def thinking_text(content, indent: 0)
156
- return "" if content.nil? || content.empty?
157
-
158
- # Strip system reminders
159
- text = Formatters::Text.strip_system_reminders(content)
160
- return "" if text.empty?
161
-
162
- prefix = " " * indent
163
-
164
- text.split("\n").map do |line|
165
- "#{prefix} #{@pastel.italic(line)}"
166
- end.join("\n")
167
- end
168
-
169
- # Render tool arguments
170
- def tool_arguments(args, indent: 0, truncate: false)
171
- @content_block.render_hash(args, indent: indent, label: "Arguments", truncate: truncate)
172
- end
173
-
174
- # Render tool result content
175
- def tool_result_content(content, indent: 0, truncate: false)
176
- @content_block.render_text(
177
- content,
178
- indent: indent,
179
- color: :bright_green,
180
- truncate: truncate,
181
- max_lines: 2,
182
- max_chars: 300,
183
- )
184
- end
185
- end
186
- end
187
- end
188
- end
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmCLI
4
- module UI
5
- module State
6
- # Caches agent name → color assignments for consistent coloring
7
- class AgentColorCache
8
- # Professional color palette inspired by modern CLIs
9
- PALETTE = [
10
- :cyan,
11
- :magenta,
12
- :yellow,
13
- :blue,
14
- :green,
15
- :bright_cyan,
16
- :bright_magenta,
17
- ].freeze
18
-
19
- def initialize
20
- @cache = {}
21
- @next_index = 0
22
- end
23
-
24
- # Get color for agent (cached)
25
- def get(agent_name)
26
- @cache[agent_name] ||= assign_next_color
27
- end
28
-
29
- # Reset cache (for testing)
30
- def reset
31
- @cache.clear
32
- @next_index = 0
33
- end
34
-
35
- private
36
-
37
- def assign_next_color
38
- color = PALETTE[@next_index % PALETTE.size]
39
- @next_index += 1
40
- color
41
- end
42
- end
43
- end
44
- end
45
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmCLI
4
- module UI
5
- module State
6
- # Tracks agent depth for hierarchical indentation display
7
- class DepthTracker
8
- def initialize
9
- @depths = {}
10
- @seen_agents = []
11
- end
12
-
13
- # Get indentation depth for agent
14
- def get(agent_name)
15
- @depths[agent_name] ||= calculate_depth(agent_name)
16
- end
17
-
18
- # Get indent string for agent
19
- def indent(agent_name, char: " ")
20
- char * get(agent_name)
21
- end
22
-
23
- # Reset tracker (for testing)
24
- def reset
25
- @depths.clear
26
- @seen_agents.clear
27
- end
28
-
29
- private
30
-
31
- def calculate_depth(agent_name)
32
- @seen_agents << agent_name unless @seen_agents.include?(agent_name)
33
-
34
- # First agent is depth 0, all others are depth 1
35
- @seen_agents.size == 1 ? 0 : 1
36
- end
37
- end
38
- end
39
- end
40
- end