swarm_cli 2.0.0.pre.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 +7 -0
- data/exe/swarm +6 -0
- data/lib/swarm_cli/cli.rb +149 -0
- data/lib/swarm_cli/commands/mcp_serve.rb +130 -0
- data/lib/swarm_cli/commands/mcp_tools.rb +146 -0
- data/lib/swarm_cli/commands/run.rb +116 -0
- data/lib/swarm_cli/formatters/human_formatter.rb +912 -0
- data/lib/swarm_cli/formatters/json_formatter.rb +51 -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/options.rb +115 -0
- data/lib/swarm_cli/version.rb +5 -0
- data/lib/swarm_cli.rb +36 -0
- metadata +283 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 796c9d34130105fe9e4a2d6896fa01e5ec73222f7c334e430b5c2d736055905e
|
4
|
+
data.tar.gz: a9abe83f5e316339e27ea9d2ca52fcffbb29d0c0efcc20dd98ba6131cf2ee2e7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 87f901492c2bc58b85b64e4a06e925eed25629e69b4732acbb07056211cb2920f65fd4c79f8305720c4cd62bac49dd20436e16864dd713ba89c6f9b922aae1f2
|
7
|
+
data.tar.gz: 0b7f8c5736f63f5c427a60a530f88f32939c654d2a2295313152b70235e95d06f3288d3ede7576b5f9f6e13f7cf2c4e12340d0d2cbf83d278c81e6f927484983
|
data/exe/swarm
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SwarmCLI
|
4
|
+
class CLI
|
5
|
+
class << self
|
6
|
+
def start(args)
|
7
|
+
SwarmSDK.refresh_models_silently
|
8
|
+
new(args).run
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(args)
|
13
|
+
@args = args
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
# Handle special cases first
|
18
|
+
if @args.empty? || @args.include?("--help") || @args.include?("-h")
|
19
|
+
print_help
|
20
|
+
exit(0)
|
21
|
+
end
|
22
|
+
|
23
|
+
if @args.include?("--version") || @args.include?("-v")
|
24
|
+
print_version
|
25
|
+
exit(0)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Extract command
|
29
|
+
command = @args.first
|
30
|
+
|
31
|
+
# Route to command
|
32
|
+
case command
|
33
|
+
when "run"
|
34
|
+
run_command(@args[1..])
|
35
|
+
when "mcp"
|
36
|
+
mcp_command(@args[1..])
|
37
|
+
else
|
38
|
+
$stderr.puts "Unknown command: #{command}"
|
39
|
+
$stderr.puts
|
40
|
+
print_help
|
41
|
+
exit(1)
|
42
|
+
end
|
43
|
+
rescue StandardError => e
|
44
|
+
$stderr.puts "Fatal error: #{e.message}"
|
45
|
+
exit(1)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def mcp_command(args)
|
51
|
+
# MCP has subcommands
|
52
|
+
subcommand = args.first
|
53
|
+
|
54
|
+
case subcommand
|
55
|
+
when "serve"
|
56
|
+
mcp_serve_command(args[1..])
|
57
|
+
when "tools"
|
58
|
+
mcp_tools_command(args[1..])
|
59
|
+
else
|
60
|
+
$stderr.puts "Unknown mcp subcommand: #{subcommand}"
|
61
|
+
$stderr.puts
|
62
|
+
$stderr.puts "Available mcp subcommands:"
|
63
|
+
$stderr.puts " serve Start an MCP server exposing swarm lead agent"
|
64
|
+
$stderr.puts " tools Start an MCP server exposing SwarmSDK tools"
|
65
|
+
exit(1)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def mcp_serve_command(args)
|
70
|
+
# Parse options
|
71
|
+
options = McpServeOptions.new
|
72
|
+
options.parse(args)
|
73
|
+
|
74
|
+
# Execute mcp serve command
|
75
|
+
Commands::McpServe.new(options).execute
|
76
|
+
rescue TTY::Option::InvalidParameter, TTY::Option::InvalidArgument => e
|
77
|
+
$stderr.puts "Error: #{e.message}"
|
78
|
+
$stderr.puts
|
79
|
+
$stderr.puts options.help
|
80
|
+
exit(1)
|
81
|
+
end
|
82
|
+
|
83
|
+
def mcp_tools_command(args)
|
84
|
+
# Parse options
|
85
|
+
options = McpToolsOptions.new
|
86
|
+
options.parse(args)
|
87
|
+
|
88
|
+
# Execute mcp tools command
|
89
|
+
Commands::McpTools.new(options).execute
|
90
|
+
rescue TTY::Option::InvalidParameter, TTY::Option::InvalidArgument => e
|
91
|
+
$stderr.puts "Error: #{e.message}"
|
92
|
+
$stderr.puts
|
93
|
+
$stderr.puts options.help
|
94
|
+
exit(1)
|
95
|
+
end
|
96
|
+
|
97
|
+
def run_command(args)
|
98
|
+
# Parse options
|
99
|
+
options = Options.new
|
100
|
+
options.parse(args)
|
101
|
+
|
102
|
+
# Execute run command
|
103
|
+
Commands::Run.new(options).execute
|
104
|
+
rescue TTY::Option::InvalidParameter, TTY::Option::InvalidArgument => e
|
105
|
+
$stderr.puts "Error: #{e.message}"
|
106
|
+
$stderr.puts
|
107
|
+
$stderr.puts options.help
|
108
|
+
exit(1)
|
109
|
+
end
|
110
|
+
|
111
|
+
def print_help
|
112
|
+
puts
|
113
|
+
puts "SwarmCLI v#{VERSION} - AI Agent Orchestration"
|
114
|
+
puts
|
115
|
+
puts "Usage:"
|
116
|
+
puts " swarm run CONFIG_FILE -p PROMPT [options]"
|
117
|
+
puts " swarm mcp serve CONFIG_FILE"
|
118
|
+
puts " swarm mcp tools [TOOL_NAMES...]"
|
119
|
+
puts
|
120
|
+
puts "Commands:"
|
121
|
+
puts " run Execute a swarm with AI agents"
|
122
|
+
puts " mcp serve Start an MCP server exposing swarm lead agent"
|
123
|
+
puts " mcp tools Start an MCP server exposing SwarmSDK tools"
|
124
|
+
puts
|
125
|
+
puts "Options:"
|
126
|
+
puts " -p, --prompt PROMPT Task prompt for the swarm"
|
127
|
+
puts " --output-format FORMAT Output format: 'human' or 'json' (default: human)"
|
128
|
+
puts " -q, --quiet Suppress progress output (human format only)"
|
129
|
+
puts " --truncate Truncate long outputs for concise view"
|
130
|
+
puts " --verbose Show system reminders and additional debug information"
|
131
|
+
puts " -h, --help Print help"
|
132
|
+
puts " -v, --version Print version"
|
133
|
+
puts
|
134
|
+
puts "Examples:"
|
135
|
+
puts " swarm run team.yml -p 'Build a REST API'"
|
136
|
+
puts " echo 'Build a REST API' | swarm run team.yml"
|
137
|
+
puts " swarm run team.yml -p 'Refactor code' --output-format json"
|
138
|
+
puts " swarm mcp serve team.yml"
|
139
|
+
puts " swarm mcp tools # Expose all SwarmSDK tools"
|
140
|
+
puts " swarm mcp tools Bash Grep Read # Space-separated tools"
|
141
|
+
puts " swarm mcp tools ScratchpadWrite,ScratchpadRead # Comma-separated tools"
|
142
|
+
puts
|
143
|
+
end
|
144
|
+
|
145
|
+
def print_version
|
146
|
+
puts "SwarmCLI v#{VERSION}"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
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,146 @@
|
|
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
|
+
@scratchpad = SwarmSDK::Scratchpad.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute
|
21
|
+
# Validate options
|
22
|
+
options.validate!
|
23
|
+
|
24
|
+
# Determine which tools to expose
|
25
|
+
tools_to_expose = determine_tools
|
26
|
+
|
27
|
+
if tools_to_expose.empty?
|
28
|
+
$stderr.puts "Error: No tools available to expose"
|
29
|
+
exit(1)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Start the MCP server
|
33
|
+
start_mcp_server(tools_to_expose)
|
34
|
+
rescue Interrupt
|
35
|
+
# User cancelled (Ctrl+C) - silent exit
|
36
|
+
exit(130)
|
37
|
+
rescue StandardError => e
|
38
|
+
# Unexpected errors - always log to stderr
|
39
|
+
$stderr.puts "Fatal error: #{e.message}"
|
40
|
+
$stderr.puts e.backtrace.first(5).join("\n") if ENV["DEBUG"]
|
41
|
+
exit(1)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def determine_tools
|
47
|
+
if options.tool_names.any?
|
48
|
+
# Use specified tools
|
49
|
+
options.tool_names.map(&:to_sym)
|
50
|
+
else
|
51
|
+
# Default: expose all available tools
|
52
|
+
SwarmSDK::Tools::Registry.available_names
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def start_mcp_server(tool_names)
|
57
|
+
require "fast_mcp"
|
58
|
+
|
59
|
+
# Create the server
|
60
|
+
server = FastMcp::Server.new(
|
61
|
+
name: "swarm-tools-server",
|
62
|
+
version: SwarmCLI::VERSION,
|
63
|
+
)
|
64
|
+
|
65
|
+
# Register each tool
|
66
|
+
tool_names.each do |tool_name|
|
67
|
+
tool_class = create_mcp_tool_wrapper(tool_name)
|
68
|
+
server.register_tool(tool_class)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Start with stdio transport (default)
|
72
|
+
server.start
|
73
|
+
end
|
74
|
+
|
75
|
+
def create_special_tool_instance(tool_name)
|
76
|
+
case tool_name
|
77
|
+
when :Read
|
78
|
+
SwarmSDK::Tools::Read.create_for_agent(:mcp)
|
79
|
+
when :Write
|
80
|
+
SwarmSDK::Tools::Write.create_for_agent(:mcp)
|
81
|
+
when :Edit
|
82
|
+
SwarmSDK::Tools::Edit.create_for_agent(:mcp)
|
83
|
+
when :MultiEdit
|
84
|
+
SwarmSDK::Tools::MultiEdit.create_for_agent(:mcp)
|
85
|
+
when :TodoWrite
|
86
|
+
SwarmSDK::Tools::TodoWrite.create_for_agent(:mcp)
|
87
|
+
when :ScratchpadWrite
|
88
|
+
SwarmSDK::Tools::ScratchpadWrite.create_for_scratchpad(@scratchpad)
|
89
|
+
when :ScratchpadRead
|
90
|
+
SwarmSDK::Tools::ScratchpadRead.create_for_scratchpad(@scratchpad)
|
91
|
+
when :ScratchpadList
|
92
|
+
SwarmSDK::Tools::ScratchpadList.create_for_scratchpad(@scratchpad)
|
93
|
+
else
|
94
|
+
raise "Unknown special tool: #{tool_name}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def create_mcp_tool_wrapper(tool_name)
|
99
|
+
sdk_tool_class_or_special = SwarmSDK::Tools::Registry.get(tool_name)
|
100
|
+
|
101
|
+
# Get the actual tool instance for special tools
|
102
|
+
sdk_tool = if sdk_tool_class_or_special == :special
|
103
|
+
create_special_tool_instance(tool_name)
|
104
|
+
else
|
105
|
+
sdk_tool_class_or_special.new
|
106
|
+
end
|
107
|
+
|
108
|
+
# Get tool metadata
|
109
|
+
tool_description = sdk_tool.respond_to?(:description) ? sdk_tool.description : "SwarmSDK #{tool_name} tool"
|
110
|
+
tool_params = sdk_tool.class.respond_to?(:parameters) ? sdk_tool.class.parameters : {}
|
111
|
+
|
112
|
+
# Create an MCP tool wrapper
|
113
|
+
Class.new(FastMcp::Tool) do
|
114
|
+
tool_name tool_name.to_s
|
115
|
+
description tool_description
|
116
|
+
|
117
|
+
# Map RubyLLM parameters to fast-mcp arguments
|
118
|
+
arguments do
|
119
|
+
tool_params.each do |param_name, param_obj|
|
120
|
+
param_type = param_obj.type == "integer" ? :integer : :string
|
121
|
+
if param_obj.required
|
122
|
+
required(param_name).filled(param_type).description(param_obj.description || "")
|
123
|
+
else
|
124
|
+
optional(param_name).filled(param_type).description(param_obj.description || "")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Capture sdk_tool in closure
|
130
|
+
define_method(:call) do |**kwargs|
|
131
|
+
result = sdk_tool.execute(**kwargs)
|
132
|
+
|
133
|
+
# Return string output for MCP
|
134
|
+
if result.is_a?(Hash)
|
135
|
+
result[:output] || result[:content] || result[:files]&.join("\n") || result.to_s
|
136
|
+
else
|
137
|
+
result.to_s
|
138
|
+
end
|
139
|
+
rescue StandardError => e
|
140
|
+
"Error: #{e.message}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,116 @@
|
|
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
|
+
# Usage:
|
8
|
+
# swarm run config.yml -p "Build a REST API"
|
9
|
+
# echo "Build a REST API" | swarm run config.yml
|
10
|
+
# swarm run config.yml -p "Task" --output-format json
|
11
|
+
#
|
12
|
+
class Run
|
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
|
24
|
+
swarm = load_swarm
|
25
|
+
|
26
|
+
# Create formatter based on output format
|
27
|
+
formatter = create_formatter
|
28
|
+
|
29
|
+
# Get prompt text
|
30
|
+
prompt = options.prompt_text
|
31
|
+
|
32
|
+
# Notify formatter of start
|
33
|
+
formatter.on_start(
|
34
|
+
config_path: options.config_file,
|
35
|
+
swarm_name: swarm.name,
|
36
|
+
lead_agent: swarm.lead_agent,
|
37
|
+
prompt: prompt,
|
38
|
+
)
|
39
|
+
|
40
|
+
# Execute swarm with logging
|
41
|
+
start_time = Time.now
|
42
|
+
result = swarm.execute(prompt) do |log_entry|
|
43
|
+
formatter.on_log(log_entry)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Check for errors
|
47
|
+
if result.failure?
|
48
|
+
duration = Time.now - start_time
|
49
|
+
formatter.on_error(error: result.error, duration: duration)
|
50
|
+
exit(1)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Notify formatter of success
|
54
|
+
formatter.on_success(result: result)
|
55
|
+
|
56
|
+
# Exit successfully
|
57
|
+
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
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def load_swarm
|
79
|
+
config_path = options.config_file
|
80
|
+
|
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)
|
86
|
+
rescue SwarmSDK::ConfigurationError => e
|
87
|
+
# Re-raise with more context
|
88
|
+
raise SwarmCLI::ExecutionError, "Configuration error: #{e.message}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def create_formatter
|
92
|
+
case options.output_format
|
93
|
+
when "json"
|
94
|
+
Formatters::JsonFormatter.new
|
95
|
+
when "human"
|
96
|
+
Formatters::HumanFormatter.new(
|
97
|
+
quiet: options.quiet?,
|
98
|
+
truncate: options.truncate?,
|
99
|
+
verbose: options.verbose?,
|
100
|
+
)
|
101
|
+
else
|
102
|
+
raise SwarmCLI::ExecutionError, "Unknown output format: #{options.output_format}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def handle_error(error, formatter: nil)
|
107
|
+
if formatter
|
108
|
+
formatter.on_error(error: error)
|
109
|
+
else
|
110
|
+
# Fallback if formatter not available
|
111
|
+
$stderr.puts "Error: #{error.message}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|