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.
- checksums.yaml +4 -4
- data/.claude/commands/release.md +1 -1
- data/.claude/hooks/lint-code-files.rb +65 -0
- data/.rubocop.yml +22 -2
- data/CHANGELOG.md +14 -1
- data/CLAUDE.md +1 -1
- data/CONTRIBUTING.md +69 -0
- data/README.md +27 -2
- data/Rakefile +71 -3
- data/analyze_coverage.rb +94 -0
- data/docs/v2/CHANGELOG.swarm_cli.md +43 -0
- data/docs/v2/CHANGELOG.swarm_memory.md +379 -0
- data/docs/v2/CHANGELOG.swarm_sdk.md +362 -0
- data/docs/v2/README.md +308 -0
- data/docs/v2/guides/claude-code-agents.md +262 -0
- data/docs/v2/guides/complete-tutorial.md +3088 -0
- data/docs/v2/guides/getting-started.md +1456 -0
- data/docs/v2/guides/memory-adapters.md +998 -0
- data/docs/v2/guides/plugins.md +816 -0
- data/docs/v2/guides/quick-start-cli.md +1745 -0
- data/docs/v2/guides/rails-integration.md +1902 -0
- data/docs/v2/guides/swarm-memory.md +599 -0
- data/docs/v2/reference/cli.md +729 -0
- data/docs/v2/reference/ruby-dsl.md +2154 -0
- data/docs/v2/reference/yaml.md +1835 -0
- data/docs-team-swarm.yml +2222 -0
- data/examples/learning-assistant/assistant.md +7 -0
- data/examples/learning-assistant/example-memories/concept-example.md +90 -0
- data/examples/learning-assistant/example-memories/experience-example.md +66 -0
- data/examples/learning-assistant/example-memories/fact-example.md +76 -0
- data/examples/learning-assistant/example-memories/memory-index.md +78 -0
- data/examples/learning-assistant/example-memories/skill-example.md +168 -0
- data/examples/learning-assistant/learning_assistant.rb +34 -0
- data/examples/learning-assistant/learning_assistant.yml +20 -0
- data/examples/v2/dsl/01_basic.rb +44 -0
- data/examples/v2/dsl/02_core_parameters.rb +59 -0
- data/examples/v2/dsl/03_capabilities.rb +71 -0
- data/examples/v2/dsl/04_llm_parameters.rb +56 -0
- data/examples/v2/dsl/05_advanced_flags.rb +73 -0
- data/examples/v2/dsl/06_permissions.rb +80 -0
- data/examples/v2/dsl/07_mcp_server.rb +62 -0
- data/examples/v2/dsl/08_swarm_hooks.rb +53 -0
- data/examples/v2/dsl/09_agent_hooks.rb +67 -0
- data/examples/v2/dsl/10_all_agents_hooks.rb +67 -0
- data/examples/v2/dsl/11_delegation.rb +60 -0
- data/examples/v2/dsl/12_complete_integration.rb +137 -0
- data/examples/v2/file_tools_swarm.yml +102 -0
- data/examples/v2/hooks/01_basic_hooks.rb +133 -0
- data/examples/v2/hooks/02_usage_tracking.rb +201 -0
- data/examples/v2/hooks/03_production_monitoring.rb +429 -0
- data/examples/v2/hooks/agent_stop_exit_0.yml +21 -0
- data/examples/v2/hooks/agent_stop_exit_1.yml +21 -0
- data/examples/v2/hooks/agent_stop_exit_2.yml +26 -0
- data/examples/v2/hooks/multiple_hooks_all_pass.yml +37 -0
- data/examples/v2/hooks/multiple_hooks_first_fails.yml +37 -0
- data/examples/v2/hooks/multiple_hooks_second_fails.yml +37 -0
- data/examples/v2/hooks/multiple_hooks_warnings.yml +37 -0
- data/examples/v2/hooks/post_tool_use_exit_0.yml +24 -0
- data/examples/v2/hooks/post_tool_use_exit_1.yml +24 -0
- data/examples/v2/hooks/post_tool_use_exit_2.yml +24 -0
- data/examples/v2/hooks/post_tool_use_multi_matcher_exit_0.yml +26 -0
- data/examples/v2/hooks/post_tool_use_multi_matcher_exit_1.yml +26 -0
- data/examples/v2/hooks/post_tool_use_multi_matcher_exit_2.yml +26 -0
- data/examples/v2/hooks/pre_tool_use_exit_0.yml +24 -0
- data/examples/v2/hooks/pre_tool_use_exit_1.yml +24 -0
- data/examples/v2/hooks/pre_tool_use_exit_2.yml +24 -0
- data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_0.yml +26 -0
- data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_1.yml +26 -0
- data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_2.yml +27 -0
- data/examples/v2/hooks/swarm_summary.sh +44 -0
- data/examples/v2/hooks/user_prompt_exit_0.yml +21 -0
- data/examples/v2/hooks/user_prompt_exit_1.yml +21 -0
- data/examples/v2/hooks/user_prompt_exit_2.yml +21 -0
- data/examples/v2/hooks/validate_bash.rb +59 -0
- data/examples/v2/multi_directory_permissions.yml +221 -0
- data/examples/v2/node_context_demo.rb +127 -0
- data/examples/v2/node_workflow.rb +173 -0
- data/examples/v2/path_resolution_demo.rb +216 -0
- data/examples/v2/simple-swarm-v2.rb +90 -0
- data/examples/v2/simple-swarm-v2.yml +62 -0
- data/examples/v2/swarm.yml +71 -0
- data/examples/v2/swarm_with_hooks.yml +61 -0
- data/examples/v2/swarm_with_hooks_simple.yml +25 -0
- data/examples/v2/think_tool_demo.rb +62 -0
- data/exe/swarm +6 -0
- data/lib/claude_swarm/claude_mcp_server.rb +0 -6
- data/lib/claude_swarm/cli.rb +10 -3
- data/lib/claude_swarm/commands/ps.rb +19 -20
- data/lib/claude_swarm/commands/show.rb +1 -1
- data/lib/claude_swarm/configuration.rb +10 -12
- data/lib/claude_swarm/mcp_generator.rb +10 -1
- data/lib/claude_swarm/orchestrator.rb +73 -49
- data/lib/claude_swarm/system_utils.rb +37 -11
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/claude_swarm/worktree_manager.rb +1 -0
- data/lib/claude_swarm/yaml_loader.rb +22 -0
- data/lib/claude_swarm.rb +6 -2
- data/lib/swarm_cli/cli.rb +201 -0
- data/lib/swarm_cli/command_registry.rb +61 -0
- data/lib/swarm_cli/commands/mcp_serve.rb +130 -0
- data/lib/swarm_cli/commands/mcp_tools.rb +148 -0
- data/lib/swarm_cli/commands/migrate.rb +55 -0
- data/lib/swarm_cli/commands/run.rb +173 -0
- data/lib/swarm_cli/config_loader.rb +97 -0
- data/lib/swarm_cli/formatters/human_formatter.rb +711 -0
- data/lib/swarm_cli/formatters/json_formatter.rb +51 -0
- data/lib/swarm_cli/interactive_repl.rb +918 -0
- data/lib/swarm_cli/mcp_serve_options.rb +44 -0
- data/lib/swarm_cli/mcp_tools_options.rb +59 -0
- data/lib/swarm_cli/migrate_options.rb +54 -0
- data/lib/swarm_cli/migrator.rb +132 -0
- data/lib/swarm_cli/options.rb +151 -0
- data/lib/swarm_cli/ui/components/agent_badge.rb +33 -0
- data/lib/swarm_cli/ui/components/content_block.rb +120 -0
- data/lib/swarm_cli/ui/components/divider.rb +57 -0
- data/lib/swarm_cli/ui/components/panel.rb +62 -0
- data/lib/swarm_cli/ui/components/usage_stats.rb +70 -0
- data/lib/swarm_cli/ui/formatters/cost.rb +49 -0
- data/lib/swarm_cli/ui/formatters/number.rb +58 -0
- data/lib/swarm_cli/ui/formatters/text.rb +77 -0
- data/lib/swarm_cli/ui/formatters/time.rb +73 -0
- data/lib/swarm_cli/ui/icons.rb +59 -0
- data/lib/swarm_cli/ui/renderers/event_renderer.rb +188 -0
- data/lib/swarm_cli/ui/state/agent_color_cache.rb +45 -0
- data/lib/swarm_cli/ui/state/depth_tracker.rb +40 -0
- data/lib/swarm_cli/ui/state/spinner_manager.rb +170 -0
- data/lib/swarm_cli/ui/state/usage_tracker.rb +62 -0
- data/lib/swarm_cli/version.rb +5 -0
- data/lib/swarm_cli.rb +44 -0
- data/lib/swarm_memory/adapters/base.rb +141 -0
- data/lib/swarm_memory/adapters/filesystem_adapter.rb +845 -0
- data/lib/swarm_memory/chat_extension.rb +34 -0
- data/lib/swarm_memory/cli/commands.rb +306 -0
- data/lib/swarm_memory/core/entry.rb +37 -0
- data/lib/swarm_memory/core/frontmatter_parser.rb +108 -0
- data/lib/swarm_memory/core/metadata_extractor.rb +68 -0
- data/lib/swarm_memory/core/path_normalizer.rb +75 -0
- data/lib/swarm_memory/core/semantic_index.rb +244 -0
- data/lib/swarm_memory/core/storage.rb +288 -0
- data/lib/swarm_memory/core/storage_read_tracker.rb +63 -0
- data/lib/swarm_memory/dsl/builder_extension.rb +40 -0
- data/lib/swarm_memory/dsl/memory_config.rb +113 -0
- data/lib/swarm_memory/embeddings/embedder.rb +36 -0
- data/lib/swarm_memory/embeddings/informers_embedder.rb +152 -0
- data/lib/swarm_memory/errors.rb +21 -0
- data/lib/swarm_memory/integration/cli_registration.rb +30 -0
- data/lib/swarm_memory/integration/configuration.rb +43 -0
- data/lib/swarm_memory/integration/registration.rb +31 -0
- data/lib/swarm_memory/integration/sdk_plugin.rb +531 -0
- data/lib/swarm_memory/optimization/analyzer.rb +244 -0
- data/lib/swarm_memory/optimization/defragmenter.rb +863 -0
- data/lib/swarm_memory/prompts/memory.md.erb +109 -0
- data/lib/swarm_memory/prompts/memory_assistant.md.erb +181 -0
- data/lib/swarm_memory/prompts/memory_researcher.md.erb +281 -0
- data/lib/swarm_memory/prompts/memory_retrieval.md.erb +78 -0
- data/lib/swarm_memory/search/semantic_search.rb +112 -0
- data/lib/swarm_memory/search/text_search.rb +42 -0
- data/lib/swarm_memory/search/text_similarity.rb +80 -0
- data/lib/swarm_memory/skills/meta/deep-learning.md +101 -0
- data/lib/swarm_memory/skills/meta/deep-learning.yml +14 -0
- data/lib/swarm_memory/tools/load_skill.rb +313 -0
- data/lib/swarm_memory/tools/memory_defrag.rb +382 -0
- data/lib/swarm_memory/tools/memory_delete.rb +99 -0
- data/lib/swarm_memory/tools/memory_edit.rb +185 -0
- data/lib/swarm_memory/tools/memory_glob.rb +160 -0
- data/lib/swarm_memory/tools/memory_grep.rb +247 -0
- data/lib/swarm_memory/tools/memory_multi_edit.rb +281 -0
- data/lib/swarm_memory/tools/memory_read.rb +123 -0
- data/lib/swarm_memory/tools/memory_write.rb +231 -0
- data/lib/swarm_memory/utils.rb +50 -0
- data/lib/swarm_memory/version.rb +5 -0
- data/lib/swarm_memory.rb +166 -0
- data/lib/swarm_sdk/agent/RETRY_LOGIC.md +127 -0
- data/lib/swarm_sdk/agent/builder.rb +461 -0
- data/lib/swarm_sdk/agent/chat/context_tracker.rb +314 -0
- data/lib/swarm_sdk/agent/chat/hook_integration.rb +372 -0
- data/lib/swarm_sdk/agent/chat/logging_helpers.rb +116 -0
- data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +152 -0
- data/lib/swarm_sdk/agent/chat.rb +1159 -0
- data/lib/swarm_sdk/agent/context.rb +112 -0
- data/lib/swarm_sdk/agent/context_manager.rb +309 -0
- data/lib/swarm_sdk/agent/definition.rb +556 -0
- data/lib/swarm_sdk/claude_code_agent_adapter.rb +205 -0
- data/lib/swarm_sdk/configuration.rb +296 -0
- data/lib/swarm_sdk/context_compactor/metrics.rb +147 -0
- data/lib/swarm_sdk/context_compactor/token_counter.rb +106 -0
- data/lib/swarm_sdk/context_compactor.rb +340 -0
- data/lib/swarm_sdk/hooks/adapter.rb +359 -0
- data/lib/swarm_sdk/hooks/context.rb +197 -0
- data/lib/swarm_sdk/hooks/definition.rb +80 -0
- data/lib/swarm_sdk/hooks/error.rb +29 -0
- data/lib/swarm_sdk/hooks/executor.rb +146 -0
- data/lib/swarm_sdk/hooks/registry.rb +147 -0
- data/lib/swarm_sdk/hooks/result.rb +150 -0
- data/lib/swarm_sdk/hooks/shell_executor.rb +254 -0
- data/lib/swarm_sdk/hooks/tool_call.rb +35 -0
- data/lib/swarm_sdk/hooks/tool_result.rb +62 -0
- data/lib/swarm_sdk/log_collector.rb +51 -0
- data/lib/swarm_sdk/log_stream.rb +69 -0
- data/lib/swarm_sdk/markdown_parser.rb +75 -0
- data/lib/swarm_sdk/model_aliases.json +5 -0
- data/lib/swarm_sdk/models.json +1 -0
- data/lib/swarm_sdk/models.rb +120 -0
- data/lib/swarm_sdk/node/agent_config.rb +49 -0
- data/lib/swarm_sdk/node/builder.rb +439 -0
- data/lib/swarm_sdk/node/transformer_executor.rb +248 -0
- data/lib/swarm_sdk/node_context.rb +170 -0
- data/lib/swarm_sdk/node_orchestrator.rb +384 -0
- data/lib/swarm_sdk/permissions/config.rb +239 -0
- data/lib/swarm_sdk/permissions/error_formatter.rb +121 -0
- data/lib/swarm_sdk/permissions/path_matcher.rb +35 -0
- data/lib/swarm_sdk/permissions/validator.rb +173 -0
- data/lib/swarm_sdk/permissions_builder.rb +122 -0
- data/lib/swarm_sdk/plugin.rb +147 -0
- data/lib/swarm_sdk/plugin_registry.rb +101 -0
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +243 -0
- data/lib/swarm_sdk/providers/openai_with_responses.rb +582 -0
- data/lib/swarm_sdk/result.rb +97 -0
- data/lib/swarm_sdk/swarm/agent_initializer.rb +334 -0
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +140 -0
- data/lib/swarm_sdk/swarm/builder.rb +586 -0
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +151 -0
- data/lib/swarm_sdk/swarm/tool_configurator.rb +419 -0
- data/lib/swarm_sdk/swarm.rb +982 -0
- data/lib/swarm_sdk/tools/bash.rb +274 -0
- data/lib/swarm_sdk/tools/clock.rb +44 -0
- data/lib/swarm_sdk/tools/delegate.rb +164 -0
- data/lib/swarm_sdk/tools/document_converters/base_converter.rb +83 -0
- data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +99 -0
- data/lib/swarm_sdk/tools/document_converters/html_converter.rb +101 -0
- data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +78 -0
- data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +194 -0
- data/lib/swarm_sdk/tools/edit.rb +150 -0
- data/lib/swarm_sdk/tools/glob.rb +158 -0
- data/lib/swarm_sdk/tools/grep.rb +228 -0
- data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +43 -0
- data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +163 -0
- data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +65 -0
- data/lib/swarm_sdk/tools/multi_edit.rb +232 -0
- data/lib/swarm_sdk/tools/path_resolver.rb +43 -0
- data/lib/swarm_sdk/tools/read.rb +251 -0
- data/lib/swarm_sdk/tools/registry.rb +93 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +96 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +76 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +91 -0
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +61 -0
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +224 -0
- data/lib/swarm_sdk/tools/stores/storage.rb +148 -0
- data/lib/swarm_sdk/tools/stores/todo_manager.rb +65 -0
- data/lib/swarm_sdk/tools/think.rb +95 -0
- data/lib/swarm_sdk/tools/todo_write.rb +216 -0
- data/lib/swarm_sdk/tools/web_fetch.rb +261 -0
- data/lib/swarm_sdk/tools/write.rb +117 -0
- data/lib/swarm_sdk/utils.rb +50 -0
- data/lib/swarm_sdk/version.rb +5 -0
- data/lib/swarm_sdk.rb +157 -0
- data/llm.v2.txt +13407 -0
- data/rubocop/cop/security/no_reflection_methods.rb +47 -0
- data/rubocop/cop/security/no_ruby_llm_logger.rb +32 -0
- data/swarm_cli.gemspec +57 -0
- data/swarm_memory.gemspec +28 -0
- data/swarm_sdk.gemspec +41 -0
- data/team.yml +1 -1
- data/team_full.yml +1875 -0
- data/{team_v2.yml → team_sdk.yml} +121 -52
- metadata +247 -4
- 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
|