claude_swarm 1.0.1 → 1.0.4
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 +7 -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 +249 -6
- data/EXAMPLES.md +0 -164
data/lib/claude_swarm/version.rb
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClaudeSwarm
|
|
4
|
+
# Provides consistent YAML loading across the application
|
|
5
|
+
module YamlLoader
|
|
6
|
+
class << self
|
|
7
|
+
# Load a YAML configuration file (enables aliases for configuration flexibility)
|
|
8
|
+
# @param file_path [String] Path to the configuration file
|
|
9
|
+
# @return [Hash] The loaded configuration
|
|
10
|
+
# @raise [ClaudeSwarm::Error] Re-raises with a more descriptive error message
|
|
11
|
+
def load_config_file(file_path)
|
|
12
|
+
YAML.load_file(file_path, aliases: true)
|
|
13
|
+
rescue Errno::ENOENT
|
|
14
|
+
raise ClaudeSwarm::Error, "Configuration file not found: #{file_path}"
|
|
15
|
+
rescue Psych::SyntaxError => e
|
|
16
|
+
raise ClaudeSwarm::Error, "Invalid YAML syntax in #{file_path}: #{e.message}"
|
|
17
|
+
rescue Psych::BadAlias => e
|
|
18
|
+
raise ClaudeSwarm::Error, "Invalid YAML alias in #{file_path}: #{e.message}"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/claude_swarm.rb
CHANGED
|
@@ -26,15 +26,17 @@ require "fast_mcp"
|
|
|
26
26
|
require "mcp_client"
|
|
27
27
|
require "thor"
|
|
28
28
|
|
|
29
|
+
require_relative "claude_swarm/version"
|
|
29
30
|
# Zeitwerk setup
|
|
30
31
|
require "zeitwerk"
|
|
31
|
-
loader = Zeitwerk::Loader.
|
|
32
|
+
loader = Zeitwerk::Loader.new
|
|
33
|
+
loader.tag = "claude_swarm"
|
|
34
|
+
|
|
32
35
|
loader.ignore("#{__dir__}/claude_swarm/templates")
|
|
33
36
|
loader.inflector.inflect(
|
|
34
37
|
"cli" => "CLI",
|
|
35
38
|
"openai" => "OpenAI",
|
|
36
39
|
)
|
|
37
|
-
loader.setup
|
|
38
40
|
|
|
39
41
|
module ClaudeSwarm
|
|
40
42
|
class Error < StandardError; end
|
|
@@ -65,3 +67,6 @@ module ClaudeSwarm
|
|
|
65
67
|
end
|
|
66
68
|
end
|
|
67
69
|
end
|
|
70
|
+
|
|
71
|
+
loader.push_dir("#{__dir__}/claude_swarm", namespace: ClaudeSwarm)
|
|
72
|
+
loader.setup
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmCLI
|
|
4
|
+
class CLI
|
|
5
|
+
class << self
|
|
6
|
+
def start(args)
|
|
7
|
+
new(args).run
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(args)
|
|
12
|
+
@args = args
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def run
|
|
16
|
+
# Handle special cases first
|
|
17
|
+
if @args.empty? || @args.include?("--help") || @args.include?("-h")
|
|
18
|
+
print_help
|
|
19
|
+
exit(0)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if @args.include?("--version") || @args.include?("-v")
|
|
23
|
+
print_version
|
|
24
|
+
exit(0)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Extract command
|
|
28
|
+
command = @args.first
|
|
29
|
+
|
|
30
|
+
# Route to command
|
|
31
|
+
case command
|
|
32
|
+
when "run"
|
|
33
|
+
run_command(@args[1..])
|
|
34
|
+
when "mcp"
|
|
35
|
+
mcp_command(@args[1..])
|
|
36
|
+
when "migrate"
|
|
37
|
+
migrate_command(@args[1..])
|
|
38
|
+
else
|
|
39
|
+
# Check if it's an extension command
|
|
40
|
+
if CommandRegistry.registered?(command)
|
|
41
|
+
extension_command(command, @args[1..])
|
|
42
|
+
else
|
|
43
|
+
$stderr.puts "Unknown command: #{command}"
|
|
44
|
+
$stderr.puts
|
|
45
|
+
print_help
|
|
46
|
+
exit(1)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
rescue StandardError => e
|
|
50
|
+
$stderr.puts "Fatal error: #{e.message}"
|
|
51
|
+
exit(1)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def mcp_command(args)
|
|
57
|
+
# MCP has subcommands
|
|
58
|
+
subcommand = args.first
|
|
59
|
+
|
|
60
|
+
case subcommand
|
|
61
|
+
when "serve"
|
|
62
|
+
mcp_serve_command(args[1..])
|
|
63
|
+
when "tools"
|
|
64
|
+
mcp_tools_command(args[1..])
|
|
65
|
+
else
|
|
66
|
+
$stderr.puts "Unknown mcp subcommand: #{subcommand}"
|
|
67
|
+
$stderr.puts
|
|
68
|
+
$stderr.puts "Available mcp subcommands:"
|
|
69
|
+
$stderr.puts " serve Start an MCP server exposing swarm lead agent"
|
|
70
|
+
$stderr.puts " tools Start an MCP server exposing SwarmSDK tools"
|
|
71
|
+
exit(1)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def mcp_serve_command(args)
|
|
76
|
+
# Parse options
|
|
77
|
+
options = McpServeOptions.new
|
|
78
|
+
options.parse(args)
|
|
79
|
+
|
|
80
|
+
# Execute mcp serve command
|
|
81
|
+
Commands::McpServe.new(options).execute
|
|
82
|
+
rescue TTY::Option::InvalidParameter, TTY::Option::InvalidArgument => e
|
|
83
|
+
$stderr.puts "Error: #{e.message}"
|
|
84
|
+
$stderr.puts
|
|
85
|
+
$stderr.puts options.help
|
|
86
|
+
exit(1)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def mcp_tools_command(args)
|
|
90
|
+
# Parse options
|
|
91
|
+
options = McpToolsOptions.new
|
|
92
|
+
options.parse(args)
|
|
93
|
+
|
|
94
|
+
# Execute mcp tools command
|
|
95
|
+
Commands::McpTools.new(options).execute
|
|
96
|
+
rescue TTY::Option::InvalidParameter, TTY::Option::InvalidArgument => e
|
|
97
|
+
$stderr.puts "Error: #{e.message}"
|
|
98
|
+
$stderr.puts
|
|
99
|
+
$stderr.puts options.help
|
|
100
|
+
exit(1)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def run_command(args)
|
|
104
|
+
# Parse options
|
|
105
|
+
options = Options.new
|
|
106
|
+
options.parse(args)
|
|
107
|
+
|
|
108
|
+
# Execute run command
|
|
109
|
+
Commands::Run.new(options).execute
|
|
110
|
+
rescue TTY::Option::InvalidParameter, TTY::Option::InvalidArgument => e
|
|
111
|
+
$stderr.puts "Error: #{e.message}"
|
|
112
|
+
$stderr.puts
|
|
113
|
+
$stderr.puts options.help
|
|
114
|
+
exit(1)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def migrate_command(args)
|
|
118
|
+
# Parse options
|
|
119
|
+
options = MigrateOptions.new
|
|
120
|
+
options.parse(args)
|
|
121
|
+
|
|
122
|
+
# Execute migrate command
|
|
123
|
+
Commands::Migrate.new(options).execute
|
|
124
|
+
rescue TTY::Option::InvalidParameter, TTY::Option::InvalidArgument => e
|
|
125
|
+
$stderr.puts "Error: #{e.message}"
|
|
126
|
+
$stderr.puts
|
|
127
|
+
$stderr.puts options.help
|
|
128
|
+
exit(1)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def extension_command(command_name, args)
|
|
132
|
+
# Get extension command class from registry
|
|
133
|
+
command_class = CommandRegistry.get(command_name)
|
|
134
|
+
|
|
135
|
+
# Execute extension command
|
|
136
|
+
command_class.execute(args)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def print_help
|
|
140
|
+
puts
|
|
141
|
+
puts "SwarmCLI v#{VERSION} - AI Agent Orchestration"
|
|
142
|
+
puts
|
|
143
|
+
puts "Usage:"
|
|
144
|
+
puts " swarm run CONFIG_FILE -p PROMPT [options]"
|
|
145
|
+
puts " swarm migrate INPUT_FILE [--output OUTPUT_FILE]"
|
|
146
|
+
puts " swarm mcp serve CONFIG_FILE"
|
|
147
|
+
puts " swarm mcp tools [TOOL_NAMES...]"
|
|
148
|
+
|
|
149
|
+
# Show extension commands dynamically
|
|
150
|
+
CommandRegistry.commands.each do |cmd|
|
|
151
|
+
puts " swarm #{cmd} ..."
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
puts
|
|
155
|
+
puts "Commands:"
|
|
156
|
+
puts " run Execute a swarm with AI agents"
|
|
157
|
+
puts " migrate Migrate Claude Swarm v1 config to SwarmSDK v2 format"
|
|
158
|
+
puts " mcp serve Start an MCP server exposing swarm lead agent"
|
|
159
|
+
puts " mcp tools Start an MCP server exposing SwarmSDK tools"
|
|
160
|
+
|
|
161
|
+
# Show extension command descriptions (if registered)
|
|
162
|
+
if CommandRegistry.registered?("memory")
|
|
163
|
+
puts " memory Manage SwarmMemory embeddings"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
puts
|
|
167
|
+
puts "Options:"
|
|
168
|
+
puts " -p, --prompt PROMPT Task prompt for the swarm"
|
|
169
|
+
puts " -o, --output FILE Output file for migrated config (default: stdout)"
|
|
170
|
+
puts " --output-format FORMAT Output format: 'human' or 'json' (default: human)"
|
|
171
|
+
puts " -q, --quiet Suppress progress output (human format only)"
|
|
172
|
+
puts " --truncate Truncate long outputs for concise view"
|
|
173
|
+
puts " --verbose Show system reminders and additional debug information"
|
|
174
|
+
puts " -h, --help Print help"
|
|
175
|
+
puts " -v, --version Print version"
|
|
176
|
+
puts
|
|
177
|
+
puts "Examples:"
|
|
178
|
+
puts " swarm run team.yml -p 'Build a REST API'"
|
|
179
|
+
puts " echo 'Build a REST API' | swarm run team.yml"
|
|
180
|
+
puts " swarm run team.yml -p 'Refactor code' --output-format json"
|
|
181
|
+
puts " swarm migrate old-config.yml"
|
|
182
|
+
puts " swarm migrate old-config.yml --output new-config.yml"
|
|
183
|
+
puts " swarm mcp serve team.yml"
|
|
184
|
+
puts " swarm mcp tools # Expose all SwarmSDK tools"
|
|
185
|
+
puts " swarm mcp tools Bash Grep Read # Space-separated tools"
|
|
186
|
+
puts " swarm mcp tools ScratchpadWrite,ScratchpadRead # Comma-separated tools"
|
|
187
|
+
|
|
188
|
+
# Show extension command examples dynamically
|
|
189
|
+
if CommandRegistry.registered?("memory")
|
|
190
|
+
puts " swarm memory setup # Setup embeddings (download model)"
|
|
191
|
+
puts " swarm memory status # Check embedding status"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
puts
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def print_version
|
|
198
|
+
puts "SwarmCLI v#{VERSION}"
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmCLI
|
|
4
|
+
# Registry for CLI command extensions
|
|
5
|
+
#
|
|
6
|
+
# Allows gems (like swarm_memory) to register additional CLI commands
|
|
7
|
+
# that integrate seamlessly with the main swarm CLI.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# # In swarm_memory gem
|
|
11
|
+
# SwarmCLI::CommandRegistry.register(:memory, MyMemoryCommand)
|
|
12
|
+
#
|
|
13
|
+
# # User runs:
|
|
14
|
+
# swarm memory status
|
|
15
|
+
#
|
|
16
|
+
# # SwarmCLI routes to MyMemoryCommand.execute(["status"])
|
|
17
|
+
class CommandRegistry
|
|
18
|
+
@extensions = {}
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
# Register a command extension
|
|
22
|
+
#
|
|
23
|
+
# @param command_name [Symbol, String] Command name (e.g., :memory)
|
|
24
|
+
# @param command_class [Class] Command class with execute(args) method
|
|
25
|
+
# @return [void]
|
|
26
|
+
#
|
|
27
|
+
# @example
|
|
28
|
+
# CommandRegistry.register(:memory, SwarmMemory::CLI::Commands)
|
|
29
|
+
def register(command_name, command_class)
|
|
30
|
+
@extensions ||= {}
|
|
31
|
+
@extensions[command_name.to_s] = command_class
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Get command class by name
|
|
35
|
+
#
|
|
36
|
+
# @param command_name [String] Command name
|
|
37
|
+
# @return [Class, nil] Command class or nil if not found
|
|
38
|
+
def get(command_name)
|
|
39
|
+
@extensions ||= {}
|
|
40
|
+
@extensions[command_name.to_s]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Check if a command is registered
|
|
44
|
+
#
|
|
45
|
+
# @param command_name [String] Command name
|
|
46
|
+
# @return [Boolean] True if command exists
|
|
47
|
+
def registered?(command_name)
|
|
48
|
+
@extensions ||= {}
|
|
49
|
+
@extensions.key?(command_name.to_s)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Get all registered command names
|
|
53
|
+
#
|
|
54
|
+
# @return [Array<String>] Command names
|
|
55
|
+
def commands
|
|
56
|
+
@extensions ||= {}
|
|
57
|
+
@extensions.keys
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmCLI
|
|
4
|
+
module Commands
|
|
5
|
+
# McpServe command starts an MCP server that exposes the swarm's lead agent as a tool.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# swarm mcp serve config.yml
|
|
9
|
+
#
|
|
10
|
+
# The server uses stdio transport and exposes a "swarm" tool that executes tasks
|
|
11
|
+
# through the configured lead agent.
|
|
12
|
+
class McpServe
|
|
13
|
+
attr_reader :options
|
|
14
|
+
|
|
15
|
+
def initialize(options)
|
|
16
|
+
@options = options
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def execute
|
|
20
|
+
# Validate options
|
|
21
|
+
options.validate!
|
|
22
|
+
|
|
23
|
+
# Load swarm configuration to validate it
|
|
24
|
+
config_path = options.config_file
|
|
25
|
+
unless File.exist?(config_path)
|
|
26
|
+
$stderr.puts "Error: Configuration file not found: #{config_path}"
|
|
27
|
+
exit(1)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Validate the swarm configuration
|
|
31
|
+
begin
|
|
32
|
+
SwarmSDK::Swarm.load(config_path)
|
|
33
|
+
rescue SwarmSDK::ConfigurationError => e
|
|
34
|
+
$stderr.puts "Error: Invalid swarm configuration: #{e.message}"
|
|
35
|
+
exit(1)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# MCP servers should be quiet - stdout is reserved for MCP protocol
|
|
39
|
+
# Errors will still be logged to stderr
|
|
40
|
+
|
|
41
|
+
# Start the MCP server
|
|
42
|
+
start_mcp_server(config_path)
|
|
43
|
+
rescue Interrupt
|
|
44
|
+
# User cancelled (Ctrl+C) - silent exit
|
|
45
|
+
exit(130)
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
# Unexpected errors - always log to stderr
|
|
48
|
+
$stderr.puts "Fatal error: #{e.message}"
|
|
49
|
+
$stderr.puts e.backtrace.first(5).join("\n") if ENV["DEBUG"]
|
|
50
|
+
exit(1)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def start_mcp_server(config_path)
|
|
56
|
+
require "fast_mcp"
|
|
57
|
+
|
|
58
|
+
# Create the server
|
|
59
|
+
server = FastMcp::Server.new(
|
|
60
|
+
name: "swarm-mcp-server",
|
|
61
|
+
version: SwarmCLI::VERSION,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Register the swarm tool
|
|
65
|
+
tool_class = create_swarm_tool_class(config_path)
|
|
66
|
+
server.register_tool(tool_class)
|
|
67
|
+
|
|
68
|
+
# Start with stdio transport (default)
|
|
69
|
+
server.start
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def create_swarm_tool_class(config_path)
|
|
73
|
+
# Create a tool class dynamically with the config path bound
|
|
74
|
+
Class.new(FastMcp::Tool) do
|
|
75
|
+
# Explicit tool name required for anonymous classes
|
|
76
|
+
tool_name "task"
|
|
77
|
+
|
|
78
|
+
description "Execute tasks through the SwarmSDK lead agent"
|
|
79
|
+
|
|
80
|
+
arguments do
|
|
81
|
+
required(:task).filled(:string).description("The task or prompt to execute")
|
|
82
|
+
optional(:description).filled(:string).description("Brief description of the task")
|
|
83
|
+
optional(:thinking_budget).filled(:string, included_in?: ["think", "think hard", "think harder", "ultrathink"]).description("Thinking budget level")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Store config path as class variable
|
|
87
|
+
@config_path = config_path
|
|
88
|
+
|
|
89
|
+
class << self
|
|
90
|
+
attr_accessor :config_path
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
define_method(:call) do |task:, description: nil, thinking_budget: nil|
|
|
94
|
+
# Load swarm for each execution (ensures fresh state)
|
|
95
|
+
swarm = SwarmSDK::Swarm.load(self.class.config_path)
|
|
96
|
+
|
|
97
|
+
# Build prompt with thinking budget if provided
|
|
98
|
+
prompt = task
|
|
99
|
+
if thinking_budget
|
|
100
|
+
prompt = "<thinking_budget>#{thinking_budget}</thinking_budget>\n\n#{task}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Execute the task (description is metadata only, not passed to execute)
|
|
104
|
+
result = swarm.execute(prompt)
|
|
105
|
+
|
|
106
|
+
# Check for errors
|
|
107
|
+
if result.failure?
|
|
108
|
+
{
|
|
109
|
+
success: false,
|
|
110
|
+
error: result.error.message,
|
|
111
|
+
task: task,
|
|
112
|
+
description: description,
|
|
113
|
+
}
|
|
114
|
+
else
|
|
115
|
+
# On success, return just the content string
|
|
116
|
+
result.content
|
|
117
|
+
end
|
|
118
|
+
rescue StandardError => e
|
|
119
|
+
{
|
|
120
|
+
success: false,
|
|
121
|
+
error: e.message,
|
|
122
|
+
task: task,
|
|
123
|
+
description: description,
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmCLI
|
|
4
|
+
module Commands
|
|
5
|
+
# McpTools command starts an MCP server that exposes SwarmSDK tools.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# swarm mcp tools # Expose all available tools
|
|
9
|
+
# swarm mcp tools Bash Grep # Expose only Bash and Grep
|
|
10
|
+
#
|
|
11
|
+
# The server uses stdio transport and exposes SwarmSDK tools as MCP tools.
|
|
12
|
+
class McpTools
|
|
13
|
+
attr_reader :options
|
|
14
|
+
|
|
15
|
+
def initialize(options)
|
|
16
|
+
@options = options
|
|
17
|
+
# Create scratchpad with persistence for MCP server
|
|
18
|
+
scratchpad_path = File.join(Dir.pwd, ".swarm", "scratchpad.json")
|
|
19
|
+
@scratchpad = SwarmSDK::Scratchpad.new(persist_to: scratchpad_path)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def execute
|
|
23
|
+
# Validate options
|
|
24
|
+
options.validate!
|
|
25
|
+
|
|
26
|
+
# Determine which tools to expose
|
|
27
|
+
tools_to_expose = determine_tools
|
|
28
|
+
|
|
29
|
+
if tools_to_expose.empty?
|
|
30
|
+
$stderr.puts "Error: No tools available to expose"
|
|
31
|
+
exit(1)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Start the MCP server
|
|
35
|
+
start_mcp_server(tools_to_expose)
|
|
36
|
+
rescue Interrupt
|
|
37
|
+
# User cancelled (Ctrl+C) - silent exit
|
|
38
|
+
exit(130)
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
# Unexpected errors - always log to stderr
|
|
41
|
+
$stderr.puts "Fatal error: #{e.message}"
|
|
42
|
+
$stderr.puts e.backtrace.first(5).join("\n") if ENV["DEBUG"]
|
|
43
|
+
exit(1)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def determine_tools
|
|
49
|
+
if options.tool_names.any?
|
|
50
|
+
# Use specified tools
|
|
51
|
+
options.tool_names.map(&:to_sym)
|
|
52
|
+
else
|
|
53
|
+
# Default: expose all available tools
|
|
54
|
+
SwarmSDK::Tools::Registry.available_names
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def start_mcp_server(tool_names)
|
|
59
|
+
require "fast_mcp"
|
|
60
|
+
|
|
61
|
+
# Create the server
|
|
62
|
+
server = FastMcp::Server.new(
|
|
63
|
+
name: "swarm-tools-server",
|
|
64
|
+
version: SwarmCLI::VERSION,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Register each tool
|
|
68
|
+
tool_names.each do |tool_name|
|
|
69
|
+
tool_class = create_mcp_tool_wrapper(tool_name)
|
|
70
|
+
server.register_tool(tool_class)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Start with stdio transport (default)
|
|
74
|
+
server.start
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def create_special_tool_instance(tool_name)
|
|
78
|
+
case tool_name
|
|
79
|
+
when :Read
|
|
80
|
+
SwarmSDK::Tools::Read.create_for_agent(:mcp)
|
|
81
|
+
when :Write
|
|
82
|
+
SwarmSDK::Tools::Write.create_for_agent(:mcp)
|
|
83
|
+
when :Edit
|
|
84
|
+
SwarmSDK::Tools::Edit.create_for_agent(:mcp)
|
|
85
|
+
when :MultiEdit
|
|
86
|
+
SwarmSDK::Tools::MultiEdit.create_for_agent(:mcp)
|
|
87
|
+
when :TodoWrite
|
|
88
|
+
SwarmSDK::Tools::TodoWrite.create_for_agent(:mcp)
|
|
89
|
+
when :ScratchpadWrite
|
|
90
|
+
SwarmSDK::Tools::ScratchpadWrite.create_for_scratchpad(@scratchpad)
|
|
91
|
+
when :ScratchpadRead
|
|
92
|
+
SwarmSDK::Tools::ScratchpadRead.create_for_scratchpad(@scratchpad)
|
|
93
|
+
when :ScratchpadList
|
|
94
|
+
SwarmSDK::Tools::ScratchpadList.create_for_scratchpad(@scratchpad)
|
|
95
|
+
else
|
|
96
|
+
raise "Unknown special tool: #{tool_name}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def create_mcp_tool_wrapper(tool_name)
|
|
101
|
+
sdk_tool_class_or_special = SwarmSDK::Tools::Registry.get(tool_name)
|
|
102
|
+
|
|
103
|
+
# Get the actual tool instance for special tools
|
|
104
|
+
sdk_tool = if sdk_tool_class_or_special == :special
|
|
105
|
+
create_special_tool_instance(tool_name)
|
|
106
|
+
else
|
|
107
|
+
sdk_tool_class_or_special.new
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Get tool metadata
|
|
111
|
+
tool_description = sdk_tool.respond_to?(:description) ? sdk_tool.description : "SwarmSDK #{tool_name} tool"
|
|
112
|
+
tool_params = sdk_tool.class.respond_to?(:parameters) ? sdk_tool.class.parameters : {}
|
|
113
|
+
|
|
114
|
+
# Create an MCP tool wrapper
|
|
115
|
+
Class.new(FastMcp::Tool) do
|
|
116
|
+
tool_name tool_name.to_s
|
|
117
|
+
description tool_description
|
|
118
|
+
|
|
119
|
+
# Map RubyLLM parameters to fast-mcp arguments
|
|
120
|
+
arguments do
|
|
121
|
+
tool_params.each do |param_name, param_obj|
|
|
122
|
+
param_type = param_obj.type == "integer" ? :integer : :string
|
|
123
|
+
if param_obj.required
|
|
124
|
+
required(param_name).filled(param_type).description(param_obj.description || "")
|
|
125
|
+
else
|
|
126
|
+
optional(param_name).filled(param_type).description(param_obj.description || "")
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Capture sdk_tool in closure
|
|
132
|
+
define_method(:call) do |**kwargs|
|
|
133
|
+
result = sdk_tool.execute(**kwargs)
|
|
134
|
+
|
|
135
|
+
# Return string output for MCP
|
|
136
|
+
if result.is_a?(Hash)
|
|
137
|
+
result[:output] || result[:content] || result[:files]&.join("\n") || result.to_s
|
|
138
|
+
else
|
|
139
|
+
result.to_s
|
|
140
|
+
end
|
|
141
|
+
rescue StandardError => e
|
|
142
|
+
"Error: #{e.message}"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmCLI
|
|
4
|
+
module Commands
|
|
5
|
+
# Migrate command converts Claude Swarm v1 configurations to SwarmSDK v2 format.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# swarm migrate old-config.yml
|
|
9
|
+
# swarm migrate old-config.yml --output new-config.yml
|
|
10
|
+
#
|
|
11
|
+
class Migrate
|
|
12
|
+
attr_reader :options
|
|
13
|
+
|
|
14
|
+
def initialize(options)
|
|
15
|
+
@options = options
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def execute
|
|
19
|
+
# Validate options
|
|
20
|
+
options.validate!
|
|
21
|
+
|
|
22
|
+
# Create migrator
|
|
23
|
+
migrator = Migrator.new(options.input_file)
|
|
24
|
+
|
|
25
|
+
# Perform migration
|
|
26
|
+
migrated_yaml = migrator.migrate
|
|
27
|
+
|
|
28
|
+
# Write to output file or stdout
|
|
29
|
+
if options.output
|
|
30
|
+
File.write(options.output, migrated_yaml)
|
|
31
|
+
$stderr.puts "✓ Migration complete! Converted configuration saved to: #{options.output}"
|
|
32
|
+
else
|
|
33
|
+
puts migrated_yaml
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
exit(0)
|
|
37
|
+
rescue SwarmCLI::ExecutionError => e
|
|
38
|
+
handle_error(e)
|
|
39
|
+
exit(1)
|
|
40
|
+
rescue Interrupt
|
|
41
|
+
$stderr.puts "\n\nMigration cancelled by user"
|
|
42
|
+
exit(130)
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
handle_error(e)
|
|
45
|
+
exit(1)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def handle_error(error)
|
|
51
|
+
$stderr.puts "Error: #{error.message}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|