swarm_cli 2.0.0.pre.1 → 2.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 796c9d34130105fe9e4a2d6896fa01e5ec73222f7c334e430b5c2d736055905e
4
- data.tar.gz: a9abe83f5e316339e27ea9d2ca52fcffbb29d0c0efcc20dd98ba6131cf2ee2e7
3
+ metadata.gz: c76f2194c98896ffdd5f1c16a08e0ce0460d3430efc9c4f21adc4999c1af73f7
4
+ data.tar.gz: c004295295b56474ba2eafad610fb2804ae9e8d171d1103b08faa5d7e93881a0
5
5
  SHA512:
6
- metadata.gz: 87f901492c2bc58b85b64e4a06e925eed25629e69b4732acbb07056211cb2920f65fd4c79f8305720c4cd62bac49dd20436e16864dd713ba89c6f9b922aae1f2
7
- data.tar.gz: 0b7f8c5736f63f5c427a60a530f88f32939c654d2a2295313152b70235e95d06f3288d3ede7576b5f9f6e13f7cf2c4e12340d0d2cbf83d278c81e6f927484983
6
+ metadata.gz: 8203e59b81a125b4d1ae7188e3a90e9158c228882e15237be1475e64a9ace7489ad0879f8a6507cdd1d0fba7f09ed448e574cbe5df424727f0ddafd06e4d4bd3
7
+ data.tar.gz: f433f4105c2adeec9e37b8d0539a81180ba5d66d0b979529acd604906fecb1c575f80c352c6620ce89faff6300d0e4ab40b76714c8af71b11e87f25f494da81e
data/lib/swarm_cli/cli.rb CHANGED
@@ -34,6 +34,8 @@ module SwarmCLI
34
34
  run_command(@args[1..])
35
35
  when "mcp"
36
36
  mcp_command(@args[1..])
37
+ when "migrate"
38
+ migrate_command(@args[1..])
37
39
  else
38
40
  $stderr.puts "Unknown command: #{command}"
39
41
  $stderr.puts
@@ -108,22 +110,39 @@ module SwarmCLI
108
110
  exit(1)
109
111
  end
110
112
 
113
+ def migrate_command(args)
114
+ # Parse options
115
+ options = MigrateOptions.new
116
+ options.parse(args)
117
+
118
+ # Execute migrate command
119
+ Commands::Migrate.new(options).execute
120
+ rescue TTY::Option::InvalidParameter, TTY::Option::InvalidArgument => e
121
+ $stderr.puts "Error: #{e.message}"
122
+ $stderr.puts
123
+ $stderr.puts options.help
124
+ exit(1)
125
+ end
126
+
111
127
  def print_help
112
128
  puts
113
129
  puts "SwarmCLI v#{VERSION} - AI Agent Orchestration"
114
130
  puts
115
131
  puts "Usage:"
116
132
  puts " swarm run CONFIG_FILE -p PROMPT [options]"
133
+ puts " swarm migrate INPUT_FILE [--output OUTPUT_FILE]"
117
134
  puts " swarm mcp serve CONFIG_FILE"
118
135
  puts " swarm mcp tools [TOOL_NAMES...]"
119
136
  puts
120
137
  puts "Commands:"
121
138
  puts " run Execute a swarm with AI agents"
139
+ puts " migrate Migrate Claude Swarm v1 config to SwarmSDK v2 format"
122
140
  puts " mcp serve Start an MCP server exposing swarm lead agent"
123
141
  puts " mcp tools Start an MCP server exposing SwarmSDK tools"
124
142
  puts
125
143
  puts "Options:"
126
144
  puts " -p, --prompt PROMPT Task prompt for the swarm"
145
+ puts " -o, --output FILE Output file for migrated config (default: stdout)"
127
146
  puts " --output-format FORMAT Output format: 'human' or 'json' (default: human)"
128
147
  puts " -q, --quiet Suppress progress output (human format only)"
129
148
  puts " --truncate Truncate long outputs for concise view"
@@ -135,6 +154,8 @@ module SwarmCLI
135
154
  puts " swarm run team.yml -p 'Build a REST API'"
136
155
  puts " echo 'Build a REST API' | swarm run team.yml"
137
156
  puts " swarm run team.yml -p 'Refactor code' --output-format json"
157
+ puts " swarm migrate old-config.yml"
158
+ puts " swarm migrate old-config.yml --output new-config.yml"
138
159
  puts " swarm mcp serve team.yml"
139
160
  puts " swarm mcp tools # Expose all SwarmSDK tools"
140
161
  puts " swarm mcp tools Bash Grep Read # Space-separated tools"
@@ -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
@@ -4,8 +4,11 @@ module SwarmCLI
4
4
  module Commands
5
5
  # Run command executes a swarm with the given configuration and prompt.
6
6
  #
7
+ # Supports both YAML (.yml, .yaml) and Ruby DSL (.rb) configuration files.
8
+ #
7
9
  # Usage:
8
10
  # swarm run config.yml -p "Build a REST API"
11
+ # swarm run config.rb -p "Build a REST API"
9
12
  # echo "Build a REST API" | swarm run config.yml
10
13
  # swarm run config.yml -p "Task" --output-format json
11
14
  #
@@ -23,6 +26,45 @@ module SwarmCLI
23
26
  # Load swarm configuration
24
27
  swarm = load_swarm
25
28
 
29
+ # Check if interactive mode (no prompt provided and stdin is a tty)
30
+ if options.interactive_mode?
31
+ run_interactive_mode(swarm)
32
+ else
33
+ run_non_interactive_mode(swarm)
34
+ end
35
+ rescue SwarmSDK::ConfigurationError, SwarmSDK::AgentNotFoundError => e
36
+ # Configuration errors - show user-friendly message
37
+ handle_error(e, formatter: create_formatter)
38
+ exit(1)
39
+ rescue SwarmCLI::ExecutionError => e
40
+ # CLI-specific errors (e.g., missing prompt)
41
+ handle_error(e, formatter: create_formatter)
42
+ exit(1)
43
+ rescue Interrupt
44
+ # User cancelled (Ctrl+C)
45
+ $stderr.puts "\n\nExecution cancelled by user"
46
+ exit(130)
47
+ rescue StandardError => e
48
+ # Unexpected errors
49
+ handle_error(e, formatter: create_formatter)
50
+ exit(1)
51
+ end
52
+
53
+ private
54
+
55
+ def run_interactive_mode(swarm)
56
+ # Launch the interactive REPL with optional initial message
57
+ initial_message = options.initial_message
58
+ repl = SwarmCLI::InteractiveREPL.new(
59
+ swarm: swarm,
60
+ options: options,
61
+ initial_message: initial_message,
62
+ )
63
+ repl.run
64
+ exit(0)
65
+ end
66
+
67
+ def run_non_interactive_mode(swarm)
26
68
  # Create formatter based on output format
27
69
  formatter = create_formatter
28
70
 
@@ -37,9 +79,15 @@ module SwarmCLI
37
79
  prompt: prompt,
38
80
  )
39
81
 
82
+ # Emit validation warnings before execution
83
+ emit_validation_warnings(swarm, formatter)
84
+
40
85
  # Execute swarm with logging
41
86
  start_time = Time.now
42
87
  result = swarm.execute(prompt) do |log_entry|
88
+ # Skip model warnings - already emitted above
89
+ next if log_entry[:type] == "model_lookup_warning"
90
+
43
91
  formatter.on_log(log_entry)
44
92
  end
45
93
 
@@ -55,36 +103,17 @@ module SwarmCLI
55
103
 
56
104
  # Exit successfully
57
105
  exit(0)
58
- rescue SwarmSDK::ConfigurationError, SwarmSDK::AgentNotFoundError => e
59
- # Configuration errors - show user-friendly message
60
- handle_error(e, formatter: create_formatter)
61
- exit(1)
62
- rescue SwarmCLI::ExecutionError => e
63
- # CLI-specific errors (e.g., missing prompt)
64
- handle_error(e, formatter: create_formatter)
65
- exit(1)
66
- rescue Interrupt
67
- # User cancelled (Ctrl+C)
68
- $stderr.puts "\n\nExecution cancelled by user"
69
- exit(130)
70
- rescue StandardError => e
71
- # Unexpected errors
72
- handle_error(e, formatter: create_formatter)
73
- exit(1)
74
106
  end
75
107
 
76
- private
77
-
78
108
  def load_swarm
79
109
  config_path = options.config_file
80
110
 
81
- unless File.exist?(config_path)
82
- raise SwarmCLI::ExecutionError, "Configuration file not found: #{config_path}"
83
- end
84
-
85
- SwarmSDK::Swarm.load(config_path)
111
+ ConfigLoader.load(config_path)
112
+ rescue SwarmCLI::ConfigurationError => e
113
+ # ConfigLoader already provides good context
114
+ raise SwarmCLI::ExecutionError, e.message
86
115
  rescue SwarmSDK::ConfigurationError => e
87
- # Re-raise with more context
116
+ # SDK errors - add context
88
117
  raise SwarmCLI::ExecutionError, "Configuration error: #{e.message}"
89
118
  end
90
119
 
@@ -103,6 +132,34 @@ module SwarmCLI
103
132
  end
104
133
  end
105
134
 
135
+ def emit_validation_warnings(swarm, formatter)
136
+ # Setup temporary logging to capture and emit warnings
137
+ SwarmSDK::LogCollector.on_log do |log_entry|
138
+ formatter.on_log(log_entry) if log_entry[:type] == "model_lookup_warning"
139
+ end
140
+
141
+ SwarmSDK::LogStream.emitter = SwarmSDK::LogCollector
142
+
143
+ # Emit validation warnings as log events
144
+ swarm.emit_validation_warnings
145
+
146
+ # Clean up
147
+ SwarmSDK::LogCollector.reset!
148
+ SwarmSDK::LogStream.reset!
149
+ rescue StandardError
150
+ # Ignore errors during validation emission
151
+ begin
152
+ SwarmSDK::LogCollector.reset!
153
+ rescue
154
+ nil
155
+ end
156
+ begin
157
+ SwarmSDK::LogStream.reset!
158
+ rescue
159
+ nil
160
+ end
161
+ end
162
+
106
163
  def handle_error(error, formatter: nil)
107
164
  if formatter
108
165
  formatter.on_error(error: error)
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmCLI
4
+ # ConfigLoader handles loading swarm configurations from both YAML and Ruby DSL files.
5
+ #
6
+ # Supports:
7
+ # - YAML files (.yml, .yaml) - loaded via SwarmSDK::Swarm.load
8
+ # - Ruby DSL files (.rb) - executed and expected to return a SwarmSDK::Swarm instance
9
+ #
10
+ # @example Load YAML config
11
+ # swarm = ConfigLoader.load("config.yml")
12
+ #
13
+ # @example Load Ruby DSL config
14
+ # swarm = ConfigLoader.load("config.rb")
15
+ #
16
+ class ConfigLoader
17
+ class << self
18
+ # Load a swarm configuration from file (YAML or Ruby DSL)
19
+ #
20
+ # Detects file type by extension:
21
+ # - .yml, .yaml -> Load as YAML using SwarmSDK::Swarm.load
22
+ # - .rb -> Execute as Ruby DSL and expect SwarmSDK::Swarm instance
23
+ #
24
+ # @param path [String, Pathname] Path to configuration file
25
+ # @return [SwarmSDK::Swarm] Configured swarm instance
26
+ # @raise [SwarmCLI::ConfigurationError] If file not found or invalid format
27
+ def load(path)
28
+ path = Pathname.new(path).expand_path
29
+
30
+ unless path.exist?
31
+ raise ConfigurationError, "Configuration file not found: #{path}"
32
+ end
33
+
34
+ case path.extname.downcase
35
+ when ".yml", ".yaml"
36
+ load_yaml(path)
37
+ when ".rb"
38
+ load_ruby_dsl(path)
39
+ else
40
+ raise ConfigurationError,
41
+ "Unsupported configuration file format: #{path.extname}. " \
42
+ "Supported formats: .yml, .yaml (YAML), .rb (Ruby DSL)"
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # Load YAML configuration file
49
+ #
50
+ # @param path [Pathname] Path to YAML file
51
+ # @return [SwarmSDK::Swarm] Configured swarm instance
52
+ def load_yaml(path)
53
+ SwarmSDK::Swarm.load(path.to_s)
54
+ rescue SwarmSDK::ConfigurationError => e
55
+ # Re-raise with CLI context
56
+ raise ConfigurationError, "Configuration error in #{path}: #{e.message}"
57
+ end
58
+
59
+ # Load Ruby DSL configuration file
60
+ #
61
+ # Executes the Ruby file in a clean binding and expects it to return
62
+ # a SwarmSDK::Swarm instance. The file should use SwarmSDK.build or
63
+ # create a Swarm instance directly.
64
+ #
65
+ # @param path [Pathname] Path to Ruby DSL file
66
+ # @return [SwarmSDK::Swarm] Configured swarm instance
67
+ # @raise [ConfigurationError] If file doesn't return a Swarm instance
68
+ def load_ruby_dsl(path)
69
+ # Read the file content
70
+ content = path.read
71
+
72
+ # Execute in a clean binding with SwarmSDK available
73
+ # This allows the DSL file to use SwarmSDK.build directly
74
+ result = eval(content, binding, path.to_s, 1) # rubocop:disable Security/Eval
75
+
76
+ # Validate result is a Swarm instance
77
+ unless result.is_a?(SwarmSDK::Swarm)
78
+ raise ConfigurationError,
79
+ "Ruby DSL file must return a SwarmSDK::Swarm instance. " \
80
+ "Got: #{result.class}. " \
81
+ "Use: SwarmSDK.build { ... } or Swarm.new(...)"
82
+ end
83
+
84
+ result
85
+ rescue SwarmSDK::ConfigurationError => e
86
+ # Re-raise SDK configuration errors with CLI context
87
+ raise ConfigurationError, "Configuration error in #{path}: #{e.message}"
88
+ rescue SyntaxError => e
89
+ # Ruby syntax errors
90
+ raise ConfigurationError, "Syntax error in #{path}: #{e.message}"
91
+ rescue StandardError => e
92
+ # Other Ruby errors during execution
93
+ raise ConfigurationError, "Error loading #{path}: #{e.message}"
94
+ end
95
+ end
96
+ end
97
+ end