swarm_cli 2.0.0.pre.1 → 2.0.1
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/lib/swarm_cli/cli.rb +21 -1
- data/lib/swarm_cli/commands/migrate.rb +55 -0
- data/lib/swarm_cli/commands/run.rb +81 -24
- data/lib/swarm_cli/config_loader.rb +97 -0
- data/lib/swarm_cli/formatters/human_formatter.rb +404 -629
- data/lib/swarm_cli/interactive_repl.rb +640 -0
- data/lib/swarm_cli/migrate_options.rb +54 -0
- data/lib/swarm_cli/migrator.rb +132 -0
- data/lib/swarm_cli/options.rb +53 -17
- 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 +1 -1
- data/lib/swarm_cli.rb +3 -1
- metadata +23 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f57ce5f511e43f8c520bf787d993cdcff34f3152870d496f72b5bbdee962c1a5
|
4
|
+
data.tar.gz: e21157d97dfce0fc71806683b6cb755c0e5ce9901c43c71764aab2b398d23fb1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8702078d6573711ebe3c08c74571c0ce3d6b1de19810fa324cf6a38122573c7268c906b35e9f3446eb812dbad7d06ce4b61a01e24ff4c2ba5d56244f2b659d0
|
7
|
+
data.tar.gz: 252d1ee2ad3acb39f4302a558ec8633a5eba854522b6924362ecbee4db9f0dfdfe92020a531333d615f1402879b3d2ea8ed9ee4ee140ec668c59a6ad5254f20e
|
data/lib/swarm_cli/cli.rb
CHANGED
@@ -4,7 +4,6 @@ module SwarmCLI
|
|
4
4
|
class CLI
|
5
5
|
class << self
|
6
6
|
def start(args)
|
7
|
-
SwarmSDK.refresh_models_silently
|
8
7
|
new(args).run
|
9
8
|
end
|
10
9
|
end
|
@@ -34,6 +33,8 @@ module SwarmCLI
|
|
34
33
|
run_command(@args[1..])
|
35
34
|
when "mcp"
|
36
35
|
mcp_command(@args[1..])
|
36
|
+
when "migrate"
|
37
|
+
migrate_command(@args[1..])
|
37
38
|
else
|
38
39
|
$stderr.puts "Unknown command: #{command}"
|
39
40
|
$stderr.puts
|
@@ -108,22 +109,39 @@ module SwarmCLI
|
|
108
109
|
exit(1)
|
109
110
|
end
|
110
111
|
|
112
|
+
def migrate_command(args)
|
113
|
+
# Parse options
|
114
|
+
options = MigrateOptions.new
|
115
|
+
options.parse(args)
|
116
|
+
|
117
|
+
# Execute migrate command
|
118
|
+
Commands::Migrate.new(options).execute
|
119
|
+
rescue TTY::Option::InvalidParameter, TTY::Option::InvalidArgument => e
|
120
|
+
$stderr.puts "Error: #{e.message}"
|
121
|
+
$stderr.puts
|
122
|
+
$stderr.puts options.help
|
123
|
+
exit(1)
|
124
|
+
end
|
125
|
+
|
111
126
|
def print_help
|
112
127
|
puts
|
113
128
|
puts "SwarmCLI v#{VERSION} - AI Agent Orchestration"
|
114
129
|
puts
|
115
130
|
puts "Usage:"
|
116
131
|
puts " swarm run CONFIG_FILE -p PROMPT [options]"
|
132
|
+
puts " swarm migrate INPUT_FILE [--output OUTPUT_FILE]"
|
117
133
|
puts " swarm mcp serve CONFIG_FILE"
|
118
134
|
puts " swarm mcp tools [TOOL_NAMES...]"
|
119
135
|
puts
|
120
136
|
puts "Commands:"
|
121
137
|
puts " run Execute a swarm with AI agents"
|
138
|
+
puts " migrate Migrate Claude Swarm v1 config to SwarmSDK v2 format"
|
122
139
|
puts " mcp serve Start an MCP server exposing swarm lead agent"
|
123
140
|
puts " mcp tools Start an MCP server exposing SwarmSDK tools"
|
124
141
|
puts
|
125
142
|
puts "Options:"
|
126
143
|
puts " -p, --prompt PROMPT Task prompt for the swarm"
|
144
|
+
puts " -o, --output FILE Output file for migrated config (default: stdout)"
|
127
145
|
puts " --output-format FORMAT Output format: 'human' or 'json' (default: human)"
|
128
146
|
puts " -q, --quiet Suppress progress output (human format only)"
|
129
147
|
puts " --truncate Truncate long outputs for concise view"
|
@@ -135,6 +153,8 @@ module SwarmCLI
|
|
135
153
|
puts " swarm run team.yml -p 'Build a REST API'"
|
136
154
|
puts " echo 'Build a REST API' | swarm run team.yml"
|
137
155
|
puts " swarm run team.yml -p 'Refactor code' --output-format json"
|
156
|
+
puts " swarm migrate old-config.yml"
|
157
|
+
puts " swarm migrate old-config.yml --output new-config.yml"
|
138
158
|
puts " swarm mcp serve team.yml"
|
139
159
|
puts " swarm mcp tools # Expose all SwarmSDK tools"
|
140
160
|
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
|
-
|
82
|
-
|
83
|
-
|
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
|
-
#
|
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
|