swarm_cli 2.1.13 → 3.0.0.alpha2
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/LICENSE +21 -0
- data/exe/swarm3 +11 -0
- data/lib/swarm_cli/v3/activity_indicator.rb +168 -0
- data/lib/swarm_cli/v3/ansi_colors.rb +70 -0
- data/lib/swarm_cli/v3/cli.rb +721 -0
- data/lib/swarm_cli/v3/command_completer.rb +112 -0
- data/lib/swarm_cli/v3/display.rb +607 -0
- data/lib/swarm_cli/v3/dropdown.rb +130 -0
- data/lib/swarm_cli/v3/event_renderer.rb +161 -0
- data/lib/swarm_cli/v3/file_completer.rb +143 -0
- data/lib/swarm_cli/v3/raw_input_reader.rb +304 -0
- data/lib/swarm_cli/v3/reboot_tool.rb +123 -0
- data/lib/swarm_cli/v3/text_input.rb +235 -0
- data/lib/swarm_cli/v3.rb +52 -0
- metadata +30 -245
- data/exe/swarm +0 -6
- data/lib/swarm_cli/cli.rb +0 -201
- data/lib/swarm_cli/command_registry.rb +0 -61
- data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
- data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
- data/lib/swarm_cli/commands/migrate.rb +0 -55
- data/lib/swarm_cli/commands/run.rb +0 -173
- data/lib/swarm_cli/config_loader.rb +0 -98
- data/lib/swarm_cli/formatters/human_formatter.rb +0 -811
- data/lib/swarm_cli/formatters/json_formatter.rb +0 -62
- data/lib/swarm_cli/interactive_repl.rb +0 -895
- data/lib/swarm_cli/mcp_serve_options.rb +0 -44
- data/lib/swarm_cli/mcp_tools_options.rb +0 -59
- data/lib/swarm_cli/migrate_options.rb +0 -54
- data/lib/swarm_cli/migrator.rb +0 -132
- data/lib/swarm_cli/options.rb +0 -151
- data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
- data/lib/swarm_cli/ui/components/content_block.rb +0 -120
- data/lib/swarm_cli/ui/components/divider.rb +0 -57
- data/lib/swarm_cli/ui/components/panel.rb +0 -62
- data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
- data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
- data/lib/swarm_cli/ui/formatters/number.rb +0 -58
- data/lib/swarm_cli/ui/formatters/text.rb +0 -77
- data/lib/swarm_cli/ui/formatters/time.rb +0 -73
- data/lib/swarm_cli/ui/icons.rb +0 -36
- data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
- data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
- data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
- data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
- data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
- data/lib/swarm_cli/version.rb +0 -5
- data/lib/swarm_cli.rb +0 -46
|
@@ -1,130 +0,0 @@
|
|
|
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.load_file(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.load_file(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
|
|
@@ -1,148 +0,0 @@
|
|
|
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 volatile scratchpad for MCP server
|
|
18
|
-
# Note: Scratchpad is always volatile - data is not persisted between sessions
|
|
19
|
-
@scratchpad = SwarmSDK::Tools::Stores::ScratchpadStorage.new
|
|
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
|
|
@@ -1,55 +0,0 @@
|
|
|
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
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SwarmCLI
|
|
4
|
-
module Commands
|
|
5
|
-
# Run command executes a swarm with the given configuration and prompt.
|
|
6
|
-
#
|
|
7
|
-
# Supports both YAML (.yml, .yaml) and Ruby DSL (.rb) configuration files.
|
|
8
|
-
#
|
|
9
|
-
# Usage:
|
|
10
|
-
# swarm run config.yml -p "Build a REST API"
|
|
11
|
-
# swarm run config.rb -p "Build a REST API"
|
|
12
|
-
# echo "Build a REST API" | swarm run config.yml
|
|
13
|
-
# swarm run config.yml -p "Task" --output-format json
|
|
14
|
-
#
|
|
15
|
-
class Run
|
|
16
|
-
attr_reader :options
|
|
17
|
-
|
|
18
|
-
def initialize(options)
|
|
19
|
-
@options = options
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def execute
|
|
23
|
-
# Validate options
|
|
24
|
-
options.validate!
|
|
25
|
-
|
|
26
|
-
# Load swarm configuration
|
|
27
|
-
swarm = load_swarm
|
|
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)
|
|
68
|
-
# Create formatter based on output format
|
|
69
|
-
formatter = create_formatter
|
|
70
|
-
|
|
71
|
-
# Get prompt text
|
|
72
|
-
prompt = options.prompt_text
|
|
73
|
-
|
|
74
|
-
# Notify formatter of start
|
|
75
|
-
formatter.on_start(
|
|
76
|
-
config_path: options.config_file,
|
|
77
|
-
swarm_name: swarm.name,
|
|
78
|
-
lead_agent: swarm.lead_agent,
|
|
79
|
-
prompt: prompt,
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
# Emit validation warnings before execution
|
|
83
|
-
emit_validation_warnings(swarm, formatter)
|
|
84
|
-
|
|
85
|
-
# Execute swarm with logging
|
|
86
|
-
start_time = Time.now
|
|
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
|
-
|
|
91
|
-
formatter.on_log(log_entry)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Check for errors
|
|
95
|
-
if result.failure?
|
|
96
|
-
duration = Time.now - start_time
|
|
97
|
-
formatter.on_error(error: result.error, duration: duration)
|
|
98
|
-
exit(1)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
# Notify formatter of success
|
|
102
|
-
formatter.on_success(result: result)
|
|
103
|
-
|
|
104
|
-
# Exit successfully
|
|
105
|
-
exit(0)
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def load_swarm
|
|
109
|
-
config_path = options.config_file
|
|
110
|
-
|
|
111
|
-
ConfigLoader.load(config_path)
|
|
112
|
-
rescue SwarmCLI::ConfigurationError => e
|
|
113
|
-
# ConfigLoader already provides good context
|
|
114
|
-
raise SwarmCLI::ExecutionError, e.message
|
|
115
|
-
rescue SwarmSDK::ConfigurationError => e
|
|
116
|
-
# SDK errors - add context
|
|
117
|
-
raise SwarmCLI::ExecutionError, "Configuration error: #{e.message}"
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def create_formatter
|
|
121
|
-
case options.output_format
|
|
122
|
-
when "json"
|
|
123
|
-
Formatters::JsonFormatter.new
|
|
124
|
-
when "human"
|
|
125
|
-
Formatters::HumanFormatter.new(
|
|
126
|
-
quiet: options.quiet?,
|
|
127
|
-
truncate: options.truncate?,
|
|
128
|
-
verbose: options.verbose?,
|
|
129
|
-
)
|
|
130
|
-
else
|
|
131
|
-
raise SwarmCLI::ExecutionError, "Unknown output format: #{options.output_format}"
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def emit_validation_warnings(swarm, formatter)
|
|
136
|
-
# Setup temporary logging to capture and emit warnings
|
|
137
|
-
SwarmSDK::LogCollector.subscribe(filter: { type: "model_lookup_warning" }) do |log_entry|
|
|
138
|
-
formatter.on_log(log_entry)
|
|
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
|
-
|
|
163
|
-
def handle_error(error, formatter: nil)
|
|
164
|
-
if formatter
|
|
165
|
-
formatter.on_error(error: error)
|
|
166
|
-
else
|
|
167
|
-
# Fallback if formatter not available
|
|
168
|
-
$stderr.puts "Error: #{error.message}"
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
end
|
|
@@ -1,98 +0,0 @@
|
|
|
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.load_file
|
|
8
|
-
# - Ruby DSL files (.rb) - executed and expected to return a SwarmSDK::Swarm or SwarmSDK::Workflow 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.load_file
|
|
22
|
-
# - .rb -> Execute as Ruby DSL and expect SwarmSDK::Swarm or SwarmSDK::Workflow instance
|
|
23
|
-
#
|
|
24
|
-
# @param path [String, Pathname] Path to configuration file
|
|
25
|
-
# @return [SwarmSDK::Swarm, SwarmSDK::Workflow] Configured swarm or workflow 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.load_file(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 or SwarmSDK::Workflow instance. The file should
|
|
63
|
-
# use SwarmSDK.build or SwarmSDK.workflow or create a Swarm/Workflow instance directly.
|
|
64
|
-
#
|
|
65
|
-
# @param path [Pathname] Path to Ruby DSL file
|
|
66
|
-
# @return [SwarmSDK::Swarm, SwarmSDK::Workflow] Configured swarm or workflow instance
|
|
67
|
-
# @raise [ConfigurationError] If file doesn't return a valid 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 or SwarmSDK.workflow directly
|
|
74
|
-
result = eval(content, binding, path.to_s, 1) # rubocop:disable Security/Eval
|
|
75
|
-
|
|
76
|
-
# Validate result is a Swarm or Workflow instance
|
|
77
|
-
# Both have the same execute(prompt) interface
|
|
78
|
-
unless result.is_a?(SwarmSDK::Swarm) || result.is_a?(SwarmSDK::Workflow)
|
|
79
|
-
raise ConfigurationError,
|
|
80
|
-
"Ruby DSL file must return a SwarmSDK::Swarm or SwarmSDK::Workflow instance. " \
|
|
81
|
-
"Got: #{result.class}. " \
|
|
82
|
-
"Use: SwarmSDK.build { ... } or SwarmSDK.workflow { ... }"
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
result
|
|
86
|
-
rescue SwarmSDK::ConfigurationError => e
|
|
87
|
-
# Re-raise SDK configuration errors with CLI context
|
|
88
|
-
raise ConfigurationError, "Configuration error in #{path}: #{e.message}"
|
|
89
|
-
rescue SyntaxError => e
|
|
90
|
-
# Ruby syntax errors
|
|
91
|
-
raise ConfigurationError, "Syntax error in #{path}: #{e.message}"
|
|
92
|
-
rescue StandardError => e
|
|
93
|
-
# Other Ruby errors during execution
|
|
94
|
-
raise ConfigurationError, "Error loading #{path}: #{e.message}"
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|