claude_swarm 1.0.1 → 1.0.2

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 (267) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/release.md +1 -1
  3. data/.claude/hooks/lint-code-files.rb +65 -0
  4. data/.rubocop.yml +22 -2
  5. data/CHANGELOG.md +14 -1
  6. data/CLAUDE.md +1 -1
  7. data/CONTRIBUTING.md +69 -0
  8. data/README.md +27 -2
  9. data/Rakefile +71 -3
  10. data/analyze_coverage.rb +94 -0
  11. data/docs/v2/CHANGELOG.swarm_cli.md +43 -0
  12. data/docs/v2/CHANGELOG.swarm_memory.md +379 -0
  13. data/docs/v2/CHANGELOG.swarm_sdk.md +362 -0
  14. data/docs/v2/README.md +308 -0
  15. data/docs/v2/guides/claude-code-agents.md +262 -0
  16. data/docs/v2/guides/complete-tutorial.md +3088 -0
  17. data/docs/v2/guides/getting-started.md +1456 -0
  18. data/docs/v2/guides/memory-adapters.md +998 -0
  19. data/docs/v2/guides/plugins.md +816 -0
  20. data/docs/v2/guides/quick-start-cli.md +1745 -0
  21. data/docs/v2/guides/rails-integration.md +1902 -0
  22. data/docs/v2/guides/swarm-memory.md +599 -0
  23. data/docs/v2/reference/cli.md +729 -0
  24. data/docs/v2/reference/ruby-dsl.md +2154 -0
  25. data/docs/v2/reference/yaml.md +1835 -0
  26. data/docs-team-swarm.yml +2222 -0
  27. data/examples/learning-assistant/assistant.md +7 -0
  28. data/examples/learning-assistant/example-memories/concept-example.md +90 -0
  29. data/examples/learning-assistant/example-memories/experience-example.md +66 -0
  30. data/examples/learning-assistant/example-memories/fact-example.md +76 -0
  31. data/examples/learning-assistant/example-memories/memory-index.md +78 -0
  32. data/examples/learning-assistant/example-memories/skill-example.md +168 -0
  33. data/examples/learning-assistant/learning_assistant.rb +34 -0
  34. data/examples/learning-assistant/learning_assistant.yml +20 -0
  35. data/examples/v2/dsl/01_basic.rb +44 -0
  36. data/examples/v2/dsl/02_core_parameters.rb +59 -0
  37. data/examples/v2/dsl/03_capabilities.rb +71 -0
  38. data/examples/v2/dsl/04_llm_parameters.rb +56 -0
  39. data/examples/v2/dsl/05_advanced_flags.rb +73 -0
  40. data/examples/v2/dsl/06_permissions.rb +80 -0
  41. data/examples/v2/dsl/07_mcp_server.rb +62 -0
  42. data/examples/v2/dsl/08_swarm_hooks.rb +53 -0
  43. data/examples/v2/dsl/09_agent_hooks.rb +67 -0
  44. data/examples/v2/dsl/10_all_agents_hooks.rb +67 -0
  45. data/examples/v2/dsl/11_delegation.rb +60 -0
  46. data/examples/v2/dsl/12_complete_integration.rb +137 -0
  47. data/examples/v2/file_tools_swarm.yml +102 -0
  48. data/examples/v2/hooks/01_basic_hooks.rb +133 -0
  49. data/examples/v2/hooks/02_usage_tracking.rb +201 -0
  50. data/examples/v2/hooks/03_production_monitoring.rb +429 -0
  51. data/examples/v2/hooks/agent_stop_exit_0.yml +21 -0
  52. data/examples/v2/hooks/agent_stop_exit_1.yml +21 -0
  53. data/examples/v2/hooks/agent_stop_exit_2.yml +26 -0
  54. data/examples/v2/hooks/multiple_hooks_all_pass.yml +37 -0
  55. data/examples/v2/hooks/multiple_hooks_first_fails.yml +37 -0
  56. data/examples/v2/hooks/multiple_hooks_second_fails.yml +37 -0
  57. data/examples/v2/hooks/multiple_hooks_warnings.yml +37 -0
  58. data/examples/v2/hooks/post_tool_use_exit_0.yml +24 -0
  59. data/examples/v2/hooks/post_tool_use_exit_1.yml +24 -0
  60. data/examples/v2/hooks/post_tool_use_exit_2.yml +24 -0
  61. data/examples/v2/hooks/post_tool_use_multi_matcher_exit_0.yml +26 -0
  62. data/examples/v2/hooks/post_tool_use_multi_matcher_exit_1.yml +26 -0
  63. data/examples/v2/hooks/post_tool_use_multi_matcher_exit_2.yml +26 -0
  64. data/examples/v2/hooks/pre_tool_use_exit_0.yml +24 -0
  65. data/examples/v2/hooks/pre_tool_use_exit_1.yml +24 -0
  66. data/examples/v2/hooks/pre_tool_use_exit_2.yml +24 -0
  67. data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_0.yml +26 -0
  68. data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_1.yml +26 -0
  69. data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_2.yml +27 -0
  70. data/examples/v2/hooks/swarm_summary.sh +44 -0
  71. data/examples/v2/hooks/user_prompt_exit_0.yml +21 -0
  72. data/examples/v2/hooks/user_prompt_exit_1.yml +21 -0
  73. data/examples/v2/hooks/user_prompt_exit_2.yml +21 -0
  74. data/examples/v2/hooks/validate_bash.rb +59 -0
  75. data/examples/v2/multi_directory_permissions.yml +221 -0
  76. data/examples/v2/node_context_demo.rb +127 -0
  77. data/examples/v2/node_workflow.rb +173 -0
  78. data/examples/v2/path_resolution_demo.rb +216 -0
  79. data/examples/v2/simple-swarm-v2.rb +90 -0
  80. data/examples/v2/simple-swarm-v2.yml +62 -0
  81. data/examples/v2/swarm.yml +71 -0
  82. data/examples/v2/swarm_with_hooks.yml +61 -0
  83. data/examples/v2/swarm_with_hooks_simple.yml +25 -0
  84. data/examples/v2/think_tool_demo.rb +62 -0
  85. data/exe/swarm +6 -0
  86. data/lib/claude_swarm/claude_mcp_server.rb +0 -6
  87. data/lib/claude_swarm/cli.rb +10 -3
  88. data/lib/claude_swarm/commands/ps.rb +19 -20
  89. data/lib/claude_swarm/commands/show.rb +1 -1
  90. data/lib/claude_swarm/configuration.rb +10 -12
  91. data/lib/claude_swarm/mcp_generator.rb +10 -1
  92. data/lib/claude_swarm/orchestrator.rb +73 -49
  93. data/lib/claude_swarm/system_utils.rb +37 -11
  94. data/lib/claude_swarm/version.rb +1 -1
  95. data/lib/claude_swarm/worktree_manager.rb +1 -0
  96. data/lib/claude_swarm/yaml_loader.rb +22 -0
  97. data/lib/claude_swarm.rb +6 -2
  98. data/lib/swarm_cli/cli.rb +201 -0
  99. data/lib/swarm_cli/command_registry.rb +61 -0
  100. data/lib/swarm_cli/commands/mcp_serve.rb +130 -0
  101. data/lib/swarm_cli/commands/mcp_tools.rb +148 -0
  102. data/lib/swarm_cli/commands/migrate.rb +55 -0
  103. data/lib/swarm_cli/commands/run.rb +173 -0
  104. data/lib/swarm_cli/config_loader.rb +97 -0
  105. data/lib/swarm_cli/formatters/human_formatter.rb +711 -0
  106. data/lib/swarm_cli/formatters/json_formatter.rb +51 -0
  107. data/lib/swarm_cli/interactive_repl.rb +918 -0
  108. data/lib/swarm_cli/mcp_serve_options.rb +44 -0
  109. data/lib/swarm_cli/mcp_tools_options.rb +59 -0
  110. data/lib/swarm_cli/migrate_options.rb +54 -0
  111. data/lib/swarm_cli/migrator.rb +132 -0
  112. data/lib/swarm_cli/options.rb +151 -0
  113. data/lib/swarm_cli/ui/components/agent_badge.rb +33 -0
  114. data/lib/swarm_cli/ui/components/content_block.rb +120 -0
  115. data/lib/swarm_cli/ui/components/divider.rb +57 -0
  116. data/lib/swarm_cli/ui/components/panel.rb +62 -0
  117. data/lib/swarm_cli/ui/components/usage_stats.rb +70 -0
  118. data/lib/swarm_cli/ui/formatters/cost.rb +49 -0
  119. data/lib/swarm_cli/ui/formatters/number.rb +58 -0
  120. data/lib/swarm_cli/ui/formatters/text.rb +77 -0
  121. data/lib/swarm_cli/ui/formatters/time.rb +73 -0
  122. data/lib/swarm_cli/ui/icons.rb +59 -0
  123. data/lib/swarm_cli/ui/renderers/event_renderer.rb +188 -0
  124. data/lib/swarm_cli/ui/state/agent_color_cache.rb +45 -0
  125. data/lib/swarm_cli/ui/state/depth_tracker.rb +40 -0
  126. data/lib/swarm_cli/ui/state/spinner_manager.rb +170 -0
  127. data/lib/swarm_cli/ui/state/usage_tracker.rb +62 -0
  128. data/lib/swarm_cli/version.rb +5 -0
  129. data/lib/swarm_cli.rb +44 -0
  130. data/lib/swarm_memory/adapters/base.rb +141 -0
  131. data/lib/swarm_memory/adapters/filesystem_adapter.rb +845 -0
  132. data/lib/swarm_memory/chat_extension.rb +34 -0
  133. data/lib/swarm_memory/cli/commands.rb +306 -0
  134. data/lib/swarm_memory/core/entry.rb +37 -0
  135. data/lib/swarm_memory/core/frontmatter_parser.rb +108 -0
  136. data/lib/swarm_memory/core/metadata_extractor.rb +68 -0
  137. data/lib/swarm_memory/core/path_normalizer.rb +75 -0
  138. data/lib/swarm_memory/core/semantic_index.rb +244 -0
  139. data/lib/swarm_memory/core/storage.rb +288 -0
  140. data/lib/swarm_memory/core/storage_read_tracker.rb +63 -0
  141. data/lib/swarm_memory/dsl/builder_extension.rb +40 -0
  142. data/lib/swarm_memory/dsl/memory_config.rb +113 -0
  143. data/lib/swarm_memory/embeddings/embedder.rb +36 -0
  144. data/lib/swarm_memory/embeddings/informers_embedder.rb +152 -0
  145. data/lib/swarm_memory/errors.rb +21 -0
  146. data/lib/swarm_memory/integration/cli_registration.rb +30 -0
  147. data/lib/swarm_memory/integration/configuration.rb +43 -0
  148. data/lib/swarm_memory/integration/registration.rb +31 -0
  149. data/lib/swarm_memory/integration/sdk_plugin.rb +531 -0
  150. data/lib/swarm_memory/optimization/analyzer.rb +244 -0
  151. data/lib/swarm_memory/optimization/defragmenter.rb +863 -0
  152. data/lib/swarm_memory/prompts/memory.md.erb +109 -0
  153. data/lib/swarm_memory/prompts/memory_assistant.md.erb +181 -0
  154. data/lib/swarm_memory/prompts/memory_researcher.md.erb +281 -0
  155. data/lib/swarm_memory/prompts/memory_retrieval.md.erb +78 -0
  156. data/lib/swarm_memory/search/semantic_search.rb +112 -0
  157. data/lib/swarm_memory/search/text_search.rb +42 -0
  158. data/lib/swarm_memory/search/text_similarity.rb +80 -0
  159. data/lib/swarm_memory/skills/meta/deep-learning.md +101 -0
  160. data/lib/swarm_memory/skills/meta/deep-learning.yml +14 -0
  161. data/lib/swarm_memory/tools/load_skill.rb +313 -0
  162. data/lib/swarm_memory/tools/memory_defrag.rb +382 -0
  163. data/lib/swarm_memory/tools/memory_delete.rb +99 -0
  164. data/lib/swarm_memory/tools/memory_edit.rb +185 -0
  165. data/lib/swarm_memory/tools/memory_glob.rb +160 -0
  166. data/lib/swarm_memory/tools/memory_grep.rb +247 -0
  167. data/lib/swarm_memory/tools/memory_multi_edit.rb +281 -0
  168. data/lib/swarm_memory/tools/memory_read.rb +123 -0
  169. data/lib/swarm_memory/tools/memory_write.rb +231 -0
  170. data/lib/swarm_memory/utils.rb +50 -0
  171. data/lib/swarm_memory/version.rb +5 -0
  172. data/lib/swarm_memory.rb +166 -0
  173. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +127 -0
  174. data/lib/swarm_sdk/agent/builder.rb +461 -0
  175. data/lib/swarm_sdk/agent/chat/context_tracker.rb +314 -0
  176. data/lib/swarm_sdk/agent/chat/hook_integration.rb +372 -0
  177. data/lib/swarm_sdk/agent/chat/logging_helpers.rb +116 -0
  178. data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +152 -0
  179. data/lib/swarm_sdk/agent/chat.rb +1159 -0
  180. data/lib/swarm_sdk/agent/context.rb +112 -0
  181. data/lib/swarm_sdk/agent/context_manager.rb +309 -0
  182. data/lib/swarm_sdk/agent/definition.rb +556 -0
  183. data/lib/swarm_sdk/claude_code_agent_adapter.rb +205 -0
  184. data/lib/swarm_sdk/configuration.rb +296 -0
  185. data/lib/swarm_sdk/context_compactor/metrics.rb +147 -0
  186. data/lib/swarm_sdk/context_compactor/token_counter.rb +106 -0
  187. data/lib/swarm_sdk/context_compactor.rb +340 -0
  188. data/lib/swarm_sdk/hooks/adapter.rb +359 -0
  189. data/lib/swarm_sdk/hooks/context.rb +197 -0
  190. data/lib/swarm_sdk/hooks/definition.rb +80 -0
  191. data/lib/swarm_sdk/hooks/error.rb +29 -0
  192. data/lib/swarm_sdk/hooks/executor.rb +146 -0
  193. data/lib/swarm_sdk/hooks/registry.rb +147 -0
  194. data/lib/swarm_sdk/hooks/result.rb +150 -0
  195. data/lib/swarm_sdk/hooks/shell_executor.rb +254 -0
  196. data/lib/swarm_sdk/hooks/tool_call.rb +35 -0
  197. data/lib/swarm_sdk/hooks/tool_result.rb +62 -0
  198. data/lib/swarm_sdk/log_collector.rb +51 -0
  199. data/lib/swarm_sdk/log_stream.rb +69 -0
  200. data/lib/swarm_sdk/markdown_parser.rb +75 -0
  201. data/lib/swarm_sdk/model_aliases.json +5 -0
  202. data/lib/swarm_sdk/models.json +1 -0
  203. data/lib/swarm_sdk/models.rb +120 -0
  204. data/lib/swarm_sdk/node/agent_config.rb +49 -0
  205. data/lib/swarm_sdk/node/builder.rb +439 -0
  206. data/lib/swarm_sdk/node/transformer_executor.rb +248 -0
  207. data/lib/swarm_sdk/node_context.rb +170 -0
  208. data/lib/swarm_sdk/node_orchestrator.rb +384 -0
  209. data/lib/swarm_sdk/permissions/config.rb +239 -0
  210. data/lib/swarm_sdk/permissions/error_formatter.rb +121 -0
  211. data/lib/swarm_sdk/permissions/path_matcher.rb +35 -0
  212. data/lib/swarm_sdk/permissions/validator.rb +173 -0
  213. data/lib/swarm_sdk/permissions_builder.rb +122 -0
  214. data/lib/swarm_sdk/plugin.rb +147 -0
  215. data/lib/swarm_sdk/plugin_registry.rb +101 -0
  216. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +243 -0
  217. data/lib/swarm_sdk/providers/openai_with_responses.rb +582 -0
  218. data/lib/swarm_sdk/result.rb +97 -0
  219. data/lib/swarm_sdk/swarm/agent_initializer.rb +334 -0
  220. data/lib/swarm_sdk/swarm/all_agents_builder.rb +140 -0
  221. data/lib/swarm_sdk/swarm/builder.rb +586 -0
  222. data/lib/swarm_sdk/swarm/mcp_configurator.rb +151 -0
  223. data/lib/swarm_sdk/swarm/tool_configurator.rb +419 -0
  224. data/lib/swarm_sdk/swarm.rb +982 -0
  225. data/lib/swarm_sdk/tools/bash.rb +274 -0
  226. data/lib/swarm_sdk/tools/clock.rb +44 -0
  227. data/lib/swarm_sdk/tools/delegate.rb +164 -0
  228. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +83 -0
  229. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +99 -0
  230. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +101 -0
  231. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +78 -0
  232. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +194 -0
  233. data/lib/swarm_sdk/tools/edit.rb +150 -0
  234. data/lib/swarm_sdk/tools/glob.rb +158 -0
  235. data/lib/swarm_sdk/tools/grep.rb +228 -0
  236. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +43 -0
  237. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +163 -0
  238. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +65 -0
  239. data/lib/swarm_sdk/tools/multi_edit.rb +232 -0
  240. data/lib/swarm_sdk/tools/path_resolver.rb +43 -0
  241. data/lib/swarm_sdk/tools/read.rb +251 -0
  242. data/lib/swarm_sdk/tools/registry.rb +93 -0
  243. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +96 -0
  244. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +76 -0
  245. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +91 -0
  246. data/lib/swarm_sdk/tools/stores/read_tracker.rb +61 -0
  247. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +224 -0
  248. data/lib/swarm_sdk/tools/stores/storage.rb +148 -0
  249. data/lib/swarm_sdk/tools/stores/todo_manager.rb +65 -0
  250. data/lib/swarm_sdk/tools/think.rb +95 -0
  251. data/lib/swarm_sdk/tools/todo_write.rb +216 -0
  252. data/lib/swarm_sdk/tools/web_fetch.rb +261 -0
  253. data/lib/swarm_sdk/tools/write.rb +117 -0
  254. data/lib/swarm_sdk/utils.rb +50 -0
  255. data/lib/swarm_sdk/version.rb +5 -0
  256. data/lib/swarm_sdk.rb +157 -0
  257. data/llm.v2.txt +13407 -0
  258. data/rubocop/cop/security/no_reflection_methods.rb +47 -0
  259. data/rubocop/cop/security/no_ruby_llm_logger.rb +32 -0
  260. data/swarm_cli.gemspec +57 -0
  261. data/swarm_memory.gemspec +28 -0
  262. data/swarm_sdk.gemspec +41 -0
  263. data/team.yml +1 -1
  264. data/team_full.yml +1875 -0
  265. data/{team_v2.yml → team_sdk.yml} +121 -52
  266. metadata +247 -4
  267. data/EXAMPLES.md +0 -164
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmCLI
4
+ # Options for the `swarm mcp serve` command
5
+ class McpServeOptions
6
+ include TTY::Option
7
+
8
+ usage do
9
+ program "swarm"
10
+ commands "mcp", "serve"
11
+ desc "Start an MCP server exposing swarm lead agent as a tool"
12
+ example "swarm mcp serve team.yml"
13
+ end
14
+
15
+ argument :config_file do
16
+ desc "Path to swarm configuration file (YAML)"
17
+ required
18
+ end
19
+
20
+ option :help do
21
+ short "-h"
22
+ long "--help"
23
+ desc "Print usage"
24
+ end
25
+
26
+ def validate!
27
+ errors = []
28
+
29
+ # Config file must exist
30
+ if config_file && !File.exist?(config_file)
31
+ errors << "Configuration file not found: #{config_file}"
32
+ end
33
+
34
+ unless errors.empty?
35
+ raise SwarmCLI::ExecutionError, errors.join("\n")
36
+ end
37
+ end
38
+
39
+ # Convenience accessor
40
+ def config_file
41
+ params[:config_file]
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmCLI
4
+ # Options for the `swarm mcp tools` command
5
+ class McpToolsOptions
6
+ include TTY::Option
7
+
8
+ usage do
9
+ program "swarm"
10
+ commands "mcp", "tools"
11
+ desc "Start an MCP server exposing SwarmSDK tools"
12
+ example "swarm mcp tools"
13
+ example "swarm mcp tools Read Write Bash"
14
+ example "swarm mcp tools ScratchpadWrite,ScratchpadRead"
15
+ end
16
+
17
+ argument :tool_names do
18
+ desc "Optional tool names to expose (defaults to all non-special tools)"
19
+ optional
20
+ arity :any
21
+ end
22
+
23
+ option :help do
24
+ short "-h"
25
+ long "--help"
26
+ desc "Print usage"
27
+ end
28
+
29
+ def validate!
30
+ errors = []
31
+
32
+ # Validate tool names if provided
33
+ if tool_names&.any?
34
+ invalid_tools = SwarmSDK::Tools::Registry.validate(tool_names)
35
+ if invalid_tools.any?
36
+ available = SwarmSDK::Tools::Registry.available_names.join(", ")
37
+ errors << "Invalid tool names: #{invalid_tools.join(", ")}. Available: #{available}"
38
+ end
39
+ end
40
+
41
+ unless errors.empty?
42
+ raise SwarmCLI::ExecutionError, errors.join("\n")
43
+ end
44
+ end
45
+
46
+ # Convenience accessor
47
+ def tool_names
48
+ names = params[:tool_names]
49
+ return [] if names.nil? || names.empty?
50
+
51
+ # TTY::Option might return a string or array
52
+ names_array = names.is_a?(Array) ? names : [names]
53
+
54
+ # Support comma-separated tool names in addition to space-separated
55
+ # e.g., both "swarm mcp tools Read Write" and "swarm mcp tools Read,Write" work
56
+ names_array.flat_map { |name| name.to_s.split(",") }.map(&:strip).reject(&:empty?)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmCLI
4
+ class MigrateOptions
5
+ include TTY::Option
6
+
7
+ usage do
8
+ program "swarm"
9
+ command "migrate"
10
+ desc "Migrate a Claude Swarm v1 configuration to SwarmSDK v2 format"
11
+ example "swarm migrate old-config.yml"
12
+ example "swarm migrate old-config.yml --output new-config.yml"
13
+ end
14
+
15
+ argument :input_file do
16
+ desc "Path to Claude Swarm v1 configuration file (YAML)"
17
+ required
18
+ end
19
+
20
+ option :output do
21
+ short "-o"
22
+ long "--output FILE"
23
+ desc "Output file path (if not specified, prints to stdout)"
24
+ end
25
+
26
+ option :help do
27
+ short "-h"
28
+ long "--help"
29
+ desc "Print usage"
30
+ end
31
+
32
+ def validate!
33
+ errors = []
34
+
35
+ # Input file must exist
36
+ if input_file && !File.exist?(input_file)
37
+ errors << "Input file not found: #{input_file}"
38
+ end
39
+
40
+ unless errors.empty?
41
+ raise SwarmCLI::ExecutionError, errors.join("\n")
42
+ end
43
+ end
44
+
45
+ # Convenience accessors that delegate to params
46
+ def input_file
47
+ params[:input_file]
48
+ end
49
+
50
+ def output
51
+ params[:output]
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmCLI
4
+ # Migrator converts Claude Swarm v1 YAML configurations to SwarmSDK v2 format.
5
+ #
6
+ # Key transformations:
7
+ # - version: 1 → 2
8
+ # - swarm.main → swarm.lead
9
+ # - swarm.instances → swarm.agents
10
+ # - For each agent:
11
+ # - prompt → system_prompt
12
+ # - connections → delegates_to
13
+ # - mcps → mcp_servers
14
+ # - allowed_tools → tools
15
+ # - vibe → bypass_permissions
16
+ # - reasoning_effort → parameters.reasoning
17
+ #
18
+ class Migrator
19
+ attr_reader :input_path
20
+
21
+ def initialize(input_path)
22
+ @input_path = input_path
23
+ end
24
+
25
+ def migrate
26
+ # Read and parse v1 YAML
27
+ v1_config = YAML.load_file(input_path)
28
+
29
+ # Validate it's a v1 config
30
+ unless v1_config["version"] == 1
31
+ raise SwarmCLI::ExecutionError, "Input file is not a v1 configuration (version: #{v1_config["version"]})"
32
+ end
33
+
34
+ # Build v2 config
35
+ v2_config = {
36
+ "version" => 2,
37
+ "swarm" => migrate_swarm(v1_config["swarm"]),
38
+ }
39
+
40
+ # Convert to YAML string
41
+ YAML.dump(v2_config)
42
+ end
43
+
44
+ private
45
+
46
+ def migrate_swarm(swarm)
47
+ v2_swarm = {
48
+ "name" => swarm["name"],
49
+ "lead" => swarm["main"], # main → lead
50
+ }
51
+
52
+ # Migrate instances → agents
53
+ if swarm["instances"]
54
+ v2_swarm["agents"] = migrate_agents(swarm["instances"])
55
+ end
56
+
57
+ v2_swarm
58
+ end
59
+
60
+ def migrate_agents(instances)
61
+ agents = {}
62
+
63
+ instances.each do |name, config|
64
+ agents[name] = migrate_agent(config)
65
+ end
66
+
67
+ agents
68
+ end
69
+
70
+ def migrate_agent(config)
71
+ agent = {}
72
+
73
+ # Copy fields that stay the same
74
+ agent["description"] = config["description"] if config["description"]
75
+ agent["directory"] = config["directory"] if config["directory"]
76
+ agent["model"] = config["model"] if config["model"]
77
+
78
+ # Migrate connections → delegates_to
79
+ agent["delegates_to"] = if config["connections"]
80
+ config["connections"]
81
+ elsif config.key?("connections") && config["connections"].nil?
82
+ # Explicit nil becomes empty array
83
+ []
84
+ else
85
+ # No connections field - add empty array for clarity
86
+ []
87
+ end
88
+
89
+ # Migrate prompt → system_prompt
90
+ agent["system_prompt"] = config["prompt"] if config["prompt"]
91
+
92
+ # Migrate mcps → mcp_servers
93
+ if config["mcps"]
94
+ agent["mcp_servers"] = config["mcps"]
95
+ end
96
+
97
+ # Migrate allowed_tools → tools
98
+ if config["allowed_tools"]
99
+ agent["tools"] = config["allowed_tools"]
100
+ end
101
+
102
+ # Migrate vibe → bypass_permissions
103
+ if config["vibe"]
104
+ agent["bypass_permissions"] = config["vibe"]
105
+ end
106
+
107
+ # Migrate reasoning_effort → parameters.reasoning
108
+ if config["reasoning_effort"]
109
+ agent["parameters"] ||= {}
110
+ agent["parameters"]["reasoning"] = config["reasoning_effort"]
111
+ end
112
+
113
+ # Copy any other fields (like provider, base_url, etc. if they exist)
114
+ # These are rare in v1 but handle them gracefully
115
+ ["provider", "base_url", "api_version"].each do |field|
116
+ agent[field] = config[field] if config[field]
117
+ end
118
+
119
+ # Handle parameters field - merge if it exists
120
+ if config["parameters"]
121
+ agent["parameters"] ||= {}
122
+ agent["parameters"].merge!(config["parameters"])
123
+ end
124
+
125
+ # Copy tools and permissions if they exist (rare in v1)
126
+ agent["tools"] = config["tools"] if config["tools"] && !config["allowed_tools"]
127
+ agent["permissions"] = config["permissions"] if config["permissions"]
128
+
129
+ agent
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmCLI
4
+ class Options
5
+ include TTY::Option
6
+
7
+ usage do
8
+ program "swarm"
9
+ command "run"
10
+ desc "Execute a swarm with AI agents"
11
+ example "swarm run team.yml # Interactive REPL"
12
+ example "swarm run team.yml 'Build a REST API' # REPL with initial message"
13
+ example "echo 'Build API' | swarm run team.yml # REPL with piped message"
14
+ example "swarm run team.yml -p 'Build a REST API' # Non-interactive mode"
15
+ example "echo 'Build API' | swarm run team.yml -p # Non-interactive from stdin"
16
+ end
17
+
18
+ argument :config_file do
19
+ desc "Path to swarm configuration file (YAML)"
20
+ required
21
+ end
22
+
23
+ argument :prompt_text do
24
+ desc "Initial message for REPL or prompt for execution (optional)"
25
+ optional
26
+ end
27
+
28
+ flag :prompt do
29
+ short "-p"
30
+ long "--prompt"
31
+ desc "Run in non-interactive mode (reads from argument or stdin)"
32
+ end
33
+
34
+ option :output_format do
35
+ long "--output-format FORMAT"
36
+ desc "Output format: 'human' (default) or 'json'"
37
+ default "human"
38
+ permit ["human", "json"]
39
+ end
40
+
41
+ option :help do
42
+ short "-h"
43
+ long "--help"
44
+ desc "Print usage"
45
+ end
46
+
47
+ option :version do
48
+ short "-v"
49
+ long "--version"
50
+ desc "Print version"
51
+ end
52
+
53
+ flag :quiet do
54
+ short "-q"
55
+ long "--quiet"
56
+ desc "Suppress progress output (human format only)"
57
+ end
58
+
59
+ flag :truncate do
60
+ long "--truncate"
61
+ desc "Truncate long outputs for concise view"
62
+ end
63
+
64
+ flag :verbose do
65
+ long "--verbose"
66
+ desc "Show system reminders and additional debug information"
67
+ end
68
+
69
+ def validate!
70
+ errors = []
71
+
72
+ # Config file must exist
73
+ if config_file && !File.exist?(config_file)
74
+ errors << "Configuration file not found: #{config_file}"
75
+ end
76
+
77
+ # Interactive mode cannot be used with JSON output
78
+ if interactive_mode? && output_format == "json"
79
+ errors << "Interactive mode is not compatible with --output-format json"
80
+ end
81
+
82
+ # Non-interactive mode requires a prompt
83
+ if non_interactive_mode? && !has_prompt_source?
84
+ errors << "Non-interactive mode (-p) requires a prompt (provide as argument or via stdin)"
85
+ end
86
+
87
+ unless errors.empty?
88
+ raise SwarmCLI::ExecutionError, errors.join("\n")
89
+ end
90
+ end
91
+
92
+ def interactive_mode?
93
+ # Interactive (REPL) mode when -p flag is NOT present
94
+ !params[:prompt]
95
+ end
96
+
97
+ def non_interactive_mode?
98
+ # Non-interactive mode when -p flag IS present
99
+ params[:prompt] == true
100
+ end
101
+
102
+ def initial_message
103
+ # For REPL mode - get initial message from argument or stdin (if piped)
104
+ return unless interactive_mode?
105
+
106
+ if params[:prompt_text] && !params[:prompt_text].empty?
107
+ params[:prompt_text]
108
+ elsif !$stdin.tty?
109
+ $stdin.read.strip
110
+ end
111
+ end
112
+
113
+ def prompt_text
114
+ # For non-interactive mode - get prompt from argument or stdin
115
+ raise SwarmCLI::ExecutionError, "Cannot get prompt_text in interactive mode" if interactive_mode?
116
+
117
+ @prompt_text ||= if params[:prompt_text] && !params[:prompt_text].empty?
118
+ params[:prompt_text]
119
+ elsif !$stdin.tty?
120
+ $stdin.read.strip
121
+ else
122
+ raise SwarmCLI::ExecutionError, "No prompt provided"
123
+ end
124
+ end
125
+
126
+ def has_prompt_source?
127
+ (params[:prompt_text] && !params[:prompt_text].empty?) || !$stdin.tty?
128
+ end
129
+
130
+ # Convenience accessors that delegate to params
131
+ def config_file
132
+ params[:config_file]
133
+ end
134
+
135
+ def output_format
136
+ params[:output_format]
137
+ end
138
+
139
+ def quiet?
140
+ params[:quiet]
141
+ end
142
+
143
+ def truncate?
144
+ params[:truncate]
145
+ end
146
+
147
+ def verbose?
148
+ params[:verbose]
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmCLI
4
+ module UI
5
+ module Components
6
+ # Renders agent names with consistent colors using cache
7
+ class AgentBadge
8
+ def initialize(pastel:, color_cache:)
9
+ @pastel = pastel
10
+ @color_cache = color_cache
11
+ end
12
+
13
+ # Render agent name with cached color
14
+ # architect → "architect" (in cyan)
15
+ def render(agent_name, icon: nil, bold: false)
16
+ color = @color_cache.get(agent_name)
17
+ text = icon ? "#{icon} #{agent_name}" : agent_name.to_s
18
+
19
+ styled = @pastel.public_send(color, text)
20
+ styled = @pastel.bold(styled) if bold
21
+
22
+ styled
23
+ end
24
+
25
+ # Render agent list (comma-separated, each colored)
26
+ # [architect, worker] → "architect, worker" (each colored differently)
27
+ def render_list(agent_names, separator: ", ")
28
+ agent_names.map { |name| render(name) }.join(separator)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmCLI
4
+ module UI
5
+ module Components
6
+ # Renders multi-line content blocks with indentation
7
+ class ContentBlock
8
+ def initialize(pastel:)
9
+ @pastel = pastel
10
+ end
11
+
12
+ # Render key-value pairs as indented block
13
+ # Arguments:
14
+ # file_path: "config.yml"
15
+ # mode: "read"
16
+ def render_hash(data, indent: 0, label: nil, truncate: false, max_value_length: 300)
17
+ return "" if data.nil? || data.empty?
18
+
19
+ lines = []
20
+ prefix = " " * indent
21
+
22
+ # Optional label
23
+ lines << "#{prefix}#{@pastel.dim("#{label}:")}" if label
24
+
25
+ # Render each key-value pair
26
+ data.each do |key, value|
27
+ formatted_value = format_value(value, truncate: truncate, max_length: max_value_length)
28
+ lines << "#{prefix} #{@pastel.cyan("#{key}:")} #{formatted_value}"
29
+ end
30
+
31
+ lines.join("\n")
32
+ end
33
+
34
+ # Render multi-line text with indentation
35
+ def render_text(text, indent: 0, color: :white, truncate: false, max_lines: nil, max_chars: nil)
36
+ return "" if text.nil? || text.empty?
37
+
38
+ prefix = " " * indent
39
+ content = text
40
+
41
+ # Strip system reminders
42
+ content = Formatters::Text.strip_system_reminders(content)
43
+ return "" if content.empty?
44
+
45
+ # Apply truncation if requested
46
+ if truncate
47
+ content, truncation_msg = Formatters::Text.truncate(
48
+ content,
49
+ lines: max_lines,
50
+ chars: max_chars,
51
+ )
52
+ end
53
+
54
+ # Render lines
55
+ lines = content.split("\n").map do |line|
56
+ "#{prefix} #{@pastel.public_send(color, line)}"
57
+ end
58
+
59
+ # Add truncation message if present
60
+ lines << "#{prefix} #{@pastel.dim(truncation_msg)}" if truncation_msg
61
+
62
+ lines.join("\n")
63
+ end
64
+
65
+ # Render list items
66
+ # • Item 1
67
+ # • Item 2
68
+ def render_list(items, indent: 0, bullet: UI::Icons::BULLET, color: :white)
69
+ return "" if items.nil? || items.empty?
70
+
71
+ prefix = " " * indent
72
+
73
+ items.map do |item|
74
+ "#{prefix} #{@pastel.public_send(color, "#{bullet} #{item}")}"
75
+ end.join("\n")
76
+ end
77
+
78
+ private
79
+
80
+ def format_value(value, truncate:, max_length:)
81
+ case value
82
+ when String
83
+ format_string_value(value, truncate: truncate, max_length: max_length)
84
+ when Array
85
+ @pastel.dim("[#{value.join(", ")}]")
86
+ when Hash
87
+ formatted = value.map { |k, v| "#{k}: #{v}" }.join(", ")
88
+ @pastel.dim("{#{formatted}}")
89
+ when Numeric
90
+ @pastel.white(value.to_s)
91
+ when TrueClass, FalseClass
92
+ @pastel.white(value.to_s)
93
+ when NilClass
94
+ @pastel.dim("nil")
95
+ else
96
+ @pastel.white(value.to_s)
97
+ end
98
+ end
99
+
100
+ def format_string_value(value, truncate:, max_length:)
101
+ return @pastel.white(value) unless truncate
102
+ return @pastel.white(value) if value.length <= max_length
103
+
104
+ lines = value.split("\n")
105
+
106
+ if lines.length > 3
107
+ # Multi-line content - show first 3 lines
108
+ preview = lines.first(3).join("\n")
109
+ line_info = "(#{lines.length} lines, #{value.length} chars)"
110
+ "#{@pastel.white(preview)}\n #{@pastel.dim("... #{line_info}")}"
111
+ else
112
+ # Single/few lines - character truncation
113
+ preview = value[0...max_length]
114
+ "#{@pastel.white(preview)}\n #{@pastel.dim("... (#{value.length} chars)")}"
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmCLI
4
+ module UI
5
+ module Components
6
+ # Divider rendering for visual separation
7
+ # Only horizontal lines - no side borders per design constraint
8
+ class Divider
9
+ def initialize(pastel:, terminal_width: 80)
10
+ @pastel = pastel
11
+ @terminal_width = terminal_width
12
+ end
13
+
14
+ # Full-width divider line
15
+ # ────────────────────────────────────────────────────────────
16
+ def full(char: "─", color: :dim)
17
+ @pastel.public_send(color, char * @terminal_width)
18
+ end
19
+
20
+ # Event separator (dotted, indented)
21
+ # ····························································
22
+ def event(indent: 0, char: "·")
23
+ prefix = " " * indent
24
+ line = char * 60
25
+ "#{prefix}#{@pastel.dim(line)}"
26
+ end
27
+
28
+ # Section divider with centered label
29
+ # ───────── Section Name ─────────
30
+ def section(label, char: "─", color: :dim)
31
+ label_width = label.length + 2 # Add spaces around label
32
+ total_line_width = @terminal_width
33
+ side_width = (total_line_width - label_width) / 2
34
+
35
+ left = char * side_width
36
+ right = char * (total_line_width - label_width - side_width)
37
+
38
+ @pastel.public_send(color, "#{left} #{label} #{right}")
39
+ end
40
+
41
+ # Top border only (no sides)
42
+ # ────────────────────────────────────────────────────────────
43
+ # Content here
44
+ def top(char: "─", color: :dim)
45
+ @pastel.public_send(color, char * @terminal_width)
46
+ end
47
+
48
+ # Bottom border only (no sides)
49
+ # Content here
50
+ # ────────────────────────────────────────────────────────────
51
+ def bottom(char: "─", color: :dim)
52
+ @pastel.public_send(color, char * @terminal_width)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end