claude_swarm 0.3.0 → 0.3.2

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: 5aaaf35a1637c4622d7b0ac938911648fa0321430f0ccd6261a41e81b904779f
4
- data.tar.gz: 510a9404f3b280da6704936aeae08ddb4d26e65b6031bd10800f50a1f709b096
3
+ metadata.gz: 7b271585c56ae3f85e921a7558bd46d82430ee60383fc999b6e89f27ffcdd0b0
4
+ data.tar.gz: 6b84e34b59412155d1a43cc00e8c41fe989287da7b98e2b12f8e1576f2002f87
5
5
  SHA512:
6
- metadata.gz: 213fe015957b8f63818642568971b4f16789a14fbc287c7f2b05c5b76afa5a8c896e679e0d025a102ed5a68c3ef1f6bc5a7e20c48f77b1a39594751c1ecdfe07
7
- data.tar.gz: f17bfdd54d60c4f5a0e1188a074d62ee9f04d0f017f8b0b261f82f45e13a065093cba44f4efc231e66fbbd0028ded498399115c07c6361a0f56d21f2893a0866
6
+ metadata.gz: 98c7d8d77ea9fb5256556fe7e73f83194cca4893084cab5b9b1826c6426052afd12b86f4fabad28e8e35d36a0114f6f7d6e1cd25fd4f02fb40f4450a5f048940
7
+ data.tar.gz: 33c134e8f553510dfee481b935c67eae21e196c4bc8bc494657042e69e32b91238169feaf0b138a632abda219fef791ad224294da958732c135b04a9fc7a1cf0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,29 @@
1
+ ## [0.3.2]
2
+
3
+ ### Added
4
+ - **Thinking budget support**: When delegating tasks between instances, orchestrators can now leverage Claude's extended thinking feature
5
+ - Connected instances automatically support thinking budgets: "think", "think hard", "think harder", "ultrathink"
6
+ - Orchestrator instances can assign thinking levels programmatically based on task complexity
7
+ - Example: Complex architectural decisions can be delegated with "think harder" while simple queries use no thinking
8
+ - Results in better quality outputs for complex tasks and faster responses for simple ones
9
+ - Works seamlessly with existing swarm configurations - no changes needed to benefit from this feature
10
+
11
+ ## [0.3.1]
12
+
13
+ ### Added
14
+ - **Interactive mode with initial prompt**: Added `-i/--interactive` flag to provide an initial prompt for interactive mode
15
+ - Use `claude-swarm -i "Your initial prompt"` to start in interactive mode with a prompt
16
+ - Cannot be used together with `-p/--prompt` (which is for non-interactive mode)
17
+ - Allows users to provide context or initial instructions while maintaining interactive session
18
+
19
+ ### Fixed
20
+ - **Development documentation**: Fixed `bundle exec` prefix in CLAUDE.md for development commands
21
+ - **Bundler environment conflicts**: Fixed issue where Claude instances would inherit bundler environment variables, causing conflicts when working in Ruby projects
22
+ - MCP servers now receive necessary Ruby/Bundler environment variables to run properly
23
+ - Claude instances (main and connected) run in clean environments via `Bundler.with_unbundled_env`
24
+ - Prevents `bundle install` and other bundler commands from using Claude Swarm's Gemfile instead of the project's Gemfile
25
+ - `claude mcp serve` now runs with a filtered environment that excludes Ruby/Bundler variables while preserving system variables
26
+
1
27
  ## [0.3.0]
2
28
 
3
29
  ### Added
data/CLAUDE.md CHANGED
@@ -8,14 +8,9 @@ Claude Swarm is a Ruby gem that orchestrates multiple Claude Code instances as a
8
8
 
9
9
  ## Development Commands
10
10
 
11
- ### Setup
12
- ```bash
13
- bin/setup # Install dependencies
14
- ```
15
-
16
11
  ### Testing
17
12
  ```bash
18
- rake test # Run the Minitest test suite
13
+ bundle exec rake test # Run the Minitest test suite
19
14
  ```
20
15
 
21
16
  **Important**: Tests should not generate any output to stdout or stderr. When writing tests:
@@ -37,7 +32,7 @@ end
37
32
 
38
33
  ### Linting
39
34
  ```bash
40
- rake rubocop -A # Run RuboCop linter to auto fix problems
35
+ bundle exec rubocop -A # Run RuboCop linter to auto fix problems
41
36
  ```
42
37
 
43
38
  ### Development Console
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Claude Swarm
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/claude_swarm.svg?cache_bust=0.2.0)](https://badge.fury.io/rb/claude_swarm)
3
+ [![Gem Version](https://badge.fury.io/rb/claude_swarm.svg?cache_bust=0.3.0)](https://badge.fury.io/rb/claude_swarm)
4
4
  [![CI](https://github.com/parruda/claude-swarm/actions/workflows/ci.yml/badge.svg)](https://github.com/parruda/claude-swarm/actions/workflows/ci.yml)
5
5
 
6
6
  Claude Swarm orchestrates multiple Claude Code instances as a collaborative AI development team. It enables running AI agents with specialized roles, tools, and directory contexts, communicating via MCP (Model Context Protocol) in a tree-like hierarchy. Define your swarm topology in simple YAML and let Claude instances delegate tasks through connected instances. Perfect for complex projects requiring specialized AI agents for frontend, backend, testing, DevOps, or research tasks.
@@ -767,6 +767,10 @@ claude-swarm --vibe
767
767
  claude-swarm -p "Implement the new user authentication feature"
768
768
  claude-swarm --prompt "Fix the bug in the payment module"
769
769
 
770
+ # Run in interactive mode with an initial prompt
771
+ claude-swarm -i "Review the codebase and suggest improvements"
772
+ claude-swarm --interactive "Help me debug this test failure"
773
+
770
774
  # Use a custom session ID instead of auto-generated UUID
771
775
  claude-swarm --session-id my-custom-session-123
772
776
 
@@ -33,43 +33,48 @@ module ClaudeSwarm
33
33
  stderr_output = []
34
34
  result_response = nil
35
35
 
36
- # Execute command with streaming
37
- Open3.popen3(*cmd_array, chdir: @working_directory) do |stdin, stdout, stderr, wait_thread|
38
- stdin.close
36
+ # Execute command with unbundled environment to avoid bundler conflicts
37
+ # This ensures claude runs in a clean environment without inheriting
38
+ # Claude Swarm's BUNDLE_* environment variables
39
+ Bundler.with_unbundled_env do
40
+ # Execute command with streaming
41
+ Open3.popen3(*cmd_array, chdir: @working_directory) do |stdin, stdout, stderr, wait_thread|
42
+ stdin.close
43
+
44
+ # Read stderr in a separate thread
45
+ stderr_thread = Thread.new do
46
+ stderr.each_line { |line| stderr_output << line }
47
+ end
39
48
 
40
- # Read stderr in a separate thread
41
- stderr_thread = Thread.new do
42
- stderr.each_line { |line| stderr_output << line }
43
- end
49
+ # Process stdout line by line
50
+ stdout.each_line do |line|
51
+ json_data = JSON.parse(line.strip)
44
52
 
45
- # Process stdout line by line
46
- stdout.each_line do |line|
47
- json_data = JSON.parse(line.strip)
53
+ # Log each JSON event
54
+ log_streaming_event(json_data)
48
55
 
49
- # Log each JSON event
50
- log_streaming_event(json_data)
56
+ # Capture session_id from system init
57
+ if json_data["type"] == "system" && json_data["subtype"] == "init"
58
+ @session_id = json_data["session_id"]
59
+ write_instance_state
60
+ end
51
61
 
52
- # Capture session_id from system init
53
- if json_data["type"] == "system" && json_data["subtype"] == "init"
54
- @session_id = json_data["session_id"]
55
- write_instance_state
62
+ # Capture the final result
63
+ result_response = json_data if json_data["type"] == "result"
64
+ rescue JSON::ParserError => e
65
+ @logger.warn("Failed to parse JSON line: #{line.strip} - #{e.message}")
56
66
  end
57
67
 
58
- # Capture the final result
59
- result_response = json_data if json_data["type"] == "result"
60
- rescue JSON::ParserError => e
61
- @logger.warn("Failed to parse JSON line: #{line.strip} - #{e.message}")
62
- end
68
+ # Wait for stderr thread to finish
69
+ stderr_thread.join
63
70
 
64
- # Wait for stderr thread to finish
65
- stderr_thread.join
66
-
67
- # Check exit status
68
- exit_status = wait_thread.value
69
- unless exit_status.success?
70
- error_msg = stderr_output.join
71
- @logger.error("Execution error for #{@instance_name}: #{error_msg}")
72
- raise ExecutionError, "Claude Code execution failed: #{error_msg}"
71
+ # Check exit status
72
+ exit_status = wait_thread.value
73
+ unless exit_status.success?
74
+ error_msg = stderr_output.join
75
+ @logger.error("Execution error for #{@instance_name}: #{error_msg}")
76
+ raise ExecutionError, "Claude Code execution failed: #{error_msg}"
77
+ end
73
78
  end
74
79
  end
75
80
 
@@ -63,10 +63,11 @@ module ClaudeSwarm
63
63
  )
64
64
 
65
65
  # Set dynamic description for TaskTool based on instance config
66
+ thinking_info = " Thinking budget levels: \"think\" < \"think hard\" < \"think harder\" < \"ultrathink\"."
66
67
  if @instance_config[:description]
67
- Tools::TaskTool.description("Execute a task using Agent #{@instance_config[:name]}. #{@instance_config[:description]}")
68
+ Tools::TaskTool.description("Execute a task using Agent #{@instance_config[:name]}. #{@instance_config[:description]} #{thinking_info}")
68
69
  else
69
- Tools::TaskTool.description("Execute a task using Agent #{@instance_config[:name]}")
70
+ Tools::TaskTool.description("Execute a task using Agent #{@instance_config[:name]}. #{thinking_info}")
70
71
  end
71
72
 
72
73
  # Register tool classes (not instances)
@@ -18,6 +18,10 @@ module ClaudeSwarm
18
18
  aliases: "-p",
19
19
  type: :string,
20
20
  desc: "Prompt to pass to the main Claude instance (non-interactive mode)"
21
+ method_option :interactive,
22
+ aliases: "-i",
23
+ type: :string,
24
+ desc: "Initial prompt for interactive mode"
21
25
  method_option :stream_logs,
22
26
  type: :boolean,
23
27
  default: false,
@@ -56,6 +60,12 @@ module ClaudeSwarm
56
60
  exit(1)
57
61
  end
58
62
 
63
+ # Validate conflicting options
64
+ if options[:prompt] && options[:interactive]
65
+ error("Cannot use both -p/--prompt and -i/--interactive")
66
+ exit(1)
67
+ end
68
+
59
69
  begin
60
70
  config = Configuration.new(config_path, base_dir: ClaudeSwarm.root_dir, options: options)
61
71
  generator = McpGenerator.new(config, vibe: options[:vibe])
@@ -64,6 +74,7 @@ module ClaudeSwarm
64
74
  generator,
65
75
  vibe: options[:vibe],
66
76
  prompt: options[:prompt],
77
+ interactive_prompt: options[:interactive],
67
78
  stream_logs: options[:stream_logs],
68
79
  debug: options[:debug],
69
80
  worktree: options[:worktree],
@@ -95,10 +95,21 @@ module ClaudeSwarm
95
95
  end
96
96
 
97
97
  def build_claude_tools_mcp_config
98
+ # Build environment for claude mcp serve by excluding Ruby/Bundler-specific variables
99
+ # This preserves all system variables while removing Ruby contamination
100
+ clean_env = ENV.to_h.reject do |key, _|
101
+ key.start_with?("BUNDLE_") ||
102
+ key.start_with?("RUBY") ||
103
+ key.start_with?("GEM_") ||
104
+ key == "RUBYOPT" ||
105
+ key == "RUBYLIB"
106
+ end
107
+
98
108
  {
99
109
  "type" => "stdio",
100
110
  "command" => "claude",
101
111
  "args" => ["mcp", "serve"],
112
+ "env" => clean_env,
102
113
  }
103
114
  end
104
115
 
@@ -161,11 +172,45 @@ module ClaudeSwarm
161
172
  args.push("--claude-session-id", claude_session_id) if claude_session_id
162
173
  end
163
174
 
164
- {
175
+ # Capture environment variables needed for Ruby and Bundler to work properly
176
+ # This includes both BUNDLE_* variables and Ruby-specific variables
177
+ required_env = {}
178
+
179
+ # Bundle-specific variables
180
+ ENV.each do |k, v|
181
+ required_env[k] = v if k.start_with?("BUNDLE_")
182
+ end
183
+
184
+ # Claude Swarm-specific variables
185
+ ENV.each do |k, v|
186
+ required_env[k] = v if k.start_with?("CLAUDE_SWARM_")
187
+ end
188
+
189
+ # Ruby-specific variables that MCP servers need
190
+ [
191
+ "RUBY_ROOT",
192
+ "RUBY_ENGINE",
193
+ "RUBY_VERSION",
194
+ "GEM_ROOT",
195
+ "GEM_HOME",
196
+ "GEM_PATH",
197
+ "RUBYOPT",
198
+ "RUBYLIB",
199
+ "PATH",
200
+ ].each do |key|
201
+ required_env[key] = ENV[key] if ENV[key]
202
+ end
203
+
204
+ config = {
165
205
  "type" => "stdio",
166
206
  "command" => exe_path,
167
207
  "args" => args,
168
208
  }
209
+
210
+ # Add required environment variables if any exist
211
+ config["env"] = required_env unless required_env.empty?
212
+
213
+ config
169
214
  end
170
215
 
171
216
  def load_instance_states
@@ -181,18 +181,23 @@ module ClaudeSwarm
181
181
  end
182
182
 
183
183
  if mcp_configs.any?
184
- @mcp_client = MCPClient.create_client(
185
- mcp_server_configs: mcp_configs,
186
- logger: @logger,
187
- )
188
-
189
- # List available tools from all MCP servers
190
- begin
191
- @available_tools = @mcp_client.list_tools
192
- @logger.info("Loaded #{@available_tools.size} tools from #{mcp_configs.size} MCP server(s)")
193
- rescue StandardError => e
194
- @logger.error("Failed to load MCP tools: #{e.message}")
195
- @available_tools = []
184
+ # Create MCP client with unbundled environment to avoid bundler conflicts
185
+ # This ensures MCP servers run in a clean environment without inheriting
186
+ # Claude Swarm's BUNDLE_* environment variables
187
+ Bundler.with_unbundled_env do
188
+ @mcp_client = MCPClient.create_client(
189
+ mcp_server_configs: mcp_configs,
190
+ logger: @logger,
191
+ )
192
+
193
+ # List available tools from all MCP servers
194
+ begin
195
+ @available_tools = @mcp_client.list_tools
196
+ @logger.info("Loaded #{@available_tools.size} tools from #{mcp_configs.size} MCP server(s)")
197
+ rescue StandardError => e
198
+ @logger.error("Failed to load MCP tools: #{e.message}")
199
+ @available_tools = []
200
+ end
196
201
  end
197
202
  end
198
203
  end
@@ -5,12 +5,13 @@ module ClaudeSwarm
5
5
  include SystemUtils
6
6
  RUN_DIR = File.expand_path("~/.claude-swarm/run")
7
7
 
8
- def initialize(configuration, mcp_generator, vibe: false, prompt: nil, stream_logs: false, debug: false,
8
+ def initialize(configuration, mcp_generator, vibe: false, prompt: nil, interactive_prompt: nil, stream_logs: false, debug: false,
9
9
  restore_session_path: nil, worktree: nil, session_id: nil)
10
10
  @config = configuration
11
11
  @generator = mcp_generator
12
12
  @vibe = vibe
13
- @prompt = prompt
13
+ @non_interactive_prompt = prompt
14
+ @interactive_prompt = interactive_prompt
14
15
  @stream_logs = stream_logs
15
16
  @debug = debug
16
17
  @restore_session_path = restore_session_path
@@ -26,7 +27,7 @@ module ClaudeSwarm
26
27
  @start_time = nil
27
28
 
28
29
  # Set environment variable for prompt mode to suppress output
29
- ENV["CLAUDE_SWARM_PROMPT"] = "1" if @prompt
30
+ ENV["CLAUDE_SWARM_PROMPT"] = "1" if @non_interactive_prompt
30
31
  end
31
32
 
32
33
  def start
@@ -34,7 +35,7 @@ module ClaudeSwarm
34
35
  @start_time = Time.now
35
36
 
36
37
  if @restore_session_path
37
- unless @prompt
38
+ unless @non_interactive_prompt
38
39
  puts "🔄 Restoring Claude Swarm: #{@config.swarm_name}"
39
40
  puts "😎 Vibe mode ON" if @vibe
40
41
  puts
@@ -49,7 +50,7 @@ module ClaudeSwarm
49
50
  # Create run symlink for restored session
50
51
  create_run_symlink
51
52
 
52
- unless @prompt
53
+ unless @non_interactive_prompt
53
54
  puts "📝 Using existing session: #{session_path}/"
54
55
  puts
55
56
  end
@@ -65,12 +66,12 @@ module ClaudeSwarm
65
66
 
66
67
  # Regenerate MCP configurations with session IDs for restoration
67
68
  @generator.generate_all
68
- unless @prompt
69
+ unless @non_interactive_prompt
69
70
  puts "✓ Regenerated MCP configurations with session IDs"
70
71
  puts
71
72
  end
72
73
  else
73
- unless @prompt
74
+ unless @non_interactive_prompt
74
75
  puts "🐝 Starting Claude Swarm: #{@config.swarm_name}"
75
76
  puts "😎 Vibe mode ON" if @vibe
76
77
  puts
@@ -94,7 +95,7 @@ module ClaudeSwarm
94
95
  # Create run symlink for new session
95
96
  create_run_symlink
96
97
 
97
- unless @prompt
98
+ unless @non_interactive_prompt
98
99
  puts "📝 Session files will be saved to: #{session_path}/"
99
100
  puts
100
101
  end
@@ -109,7 +110,7 @@ module ClaudeSwarm
109
110
  if @needs_worktree_manager
110
111
  cli_option = @worktree_option.is_a?(String) && !@worktree_option.empty? ? @worktree_option : nil
111
112
  @worktree_manager = WorktreeManager.new(cli_option, session_id: @session_id)
112
- puts "🌳 Setting up Git worktrees..." unless @prompt
113
+ puts "🌳 Setting up Git worktrees..." unless @non_interactive_prompt
113
114
 
114
115
  # Get all instances for worktree setup
115
116
  # Note: instances.values already includes the main instance
@@ -117,7 +118,7 @@ module ClaudeSwarm
117
118
 
118
119
  @worktree_manager.setup_worktrees(all_instances)
119
120
 
120
- unless @prompt
121
+ unless @non_interactive_prompt
121
122
  puts "✓ Worktrees created with branch: #{@worktree_manager.worktree_name}"
122
123
  puts
123
124
  end
@@ -125,7 +126,7 @@ module ClaudeSwarm
125
126
 
126
127
  # Generate all MCP configuration files
127
128
  @generator.generate_all
128
- unless @prompt
129
+ unless @non_interactive_prompt
129
130
  puts "✓ Generated MCP configurations in session directory"
130
131
  puts
131
132
  end
@@ -136,7 +137,7 @@ module ClaudeSwarm
136
137
 
137
138
  # Launch the main instance (fetch after worktree setup to get modified paths)
138
139
  main_instance = @config.main_instance_config
139
- unless @prompt
140
+ unless @non_interactive_prompt
140
141
  puts "🚀 Launching main instance: #{@config.main_instance}"
141
142
  puts " Model: #{main_instance[:model]}"
142
143
  if main_instance[:directories].size == 1
@@ -153,14 +154,14 @@ module ClaudeSwarm
153
154
  end
154
155
 
155
156
  command = build_main_command(main_instance)
156
- if @debug && !@prompt
157
+ if @debug && !@non_interactive_prompt
157
158
  puts "🏃 Running: #{format_command_for_display(command)}"
158
159
  puts
159
160
  end
160
161
 
161
162
  # Start log streaming thread if in non-interactive mode with --stream-logs
162
163
  log_thread = nil
163
- log_thread = start_log_streaming if @prompt && @stream_logs
164
+ log_thread = start_log_streaming if @non_interactive_prompt && @stream_logs
164
165
 
165
166
  # Write the current process PID (orchestrator) to a file for easy access
166
167
  main_pid_file = File.join(@session_path, "main_pid")
@@ -171,27 +172,32 @@ module ClaudeSwarm
171
172
  # Execute before commands if specified
172
173
  before_commands = @config.before_commands
173
174
  if before_commands.any? && !@restore_session_path
174
- unless @prompt
175
+ unless @non_interactive_prompt
175
176
  puts "⚙️ Executing before commands..."
176
177
  puts
177
178
  end
178
179
 
179
180
  success = execute_before_commands?(before_commands)
180
181
  unless success
181
- puts "❌ Before commands failed. Aborting swarm launch." unless @prompt
182
+ puts "❌ Before commands failed. Aborting swarm launch." unless @non_interactive_prompt
182
183
  cleanup_processes
183
184
  cleanup_run_symlink
184
185
  cleanup_worktrees
185
186
  exit(1)
186
187
  end
187
188
 
188
- unless @prompt
189
+ unless @non_interactive_prompt
189
190
  puts "✓ Before commands completed successfully"
190
191
  puts
191
192
  end
192
193
  end
193
194
 
194
- system!(*command)
195
+ # Execute main Claude instance with unbundled environment to avoid bundler conflicts
196
+ # This ensures the main instance runs in a clean environment without inheriting
197
+ # Claude Swarm's BUNDLE_* environment variables
198
+ Bundler.with_unbundled_env do
199
+ system!(*command)
200
+ end
195
201
  end
196
202
 
197
203
  # Clean up log streaming thread
@@ -207,14 +213,14 @@ module ClaudeSwarm
207
213
  after_commands = @config.after_commands
208
214
  if after_commands.any? && !@restore_session_path
209
215
  Dir.chdir(main_instance[:directory]) do
210
- unless @prompt
216
+ unless @non_interactive_prompt
211
217
  puts
212
218
  puts "⚙️ Executing after commands..."
213
219
  puts
214
220
  end
215
221
 
216
222
  success = execute_after_commands?(after_commands)
217
- if !success && !@prompt
223
+ if !success && !@non_interactive_prompt
218
224
  puts "⚠️ Some after commands failed"
219
225
  puts
220
226
  end
@@ -242,7 +248,7 @@ module ClaudeSwarm
242
248
 
243
249
  # Execute the command and capture output
244
250
  begin
245
- puts "Debug: Executing command #{index + 1}/#{commands.size}: #{command}" if @debug && !@prompt
251
+ puts "Debug: Executing command #{index + 1}/#{commands.size}: #{command}" if @debug && !@non_interactive_prompt
246
252
 
247
253
  # Use system with output capture
248
254
  output = %x(#{command} 2>&1)
@@ -259,18 +265,18 @@ module ClaudeSwarm
259
265
  end
260
266
 
261
267
  # Show output if in debug mode or if command failed
262
- if (@debug || !success) && !@prompt
268
+ if (@debug || !success) && !@non_interactive_prompt
263
269
  puts "Command #{index + 1} output:"
264
270
  puts output
265
271
  puts "Exit status: #{$CHILD_STATUS.exitstatus}"
266
272
  end
267
273
 
268
274
  unless success
269
- puts "❌ Before command #{index + 1} failed: #{command}" unless @prompt
275
+ puts "❌ Before command #{index + 1} failed: #{command}" unless @non_interactive_prompt
270
276
  return false
271
277
  end
272
278
  rescue StandardError => e
273
- puts "Error executing before command #{index + 1}: #{e.message}" unless @prompt
279
+ puts "Error executing before command #{index + 1}: #{e.message}" unless @non_interactive_prompt
274
280
  if @session_path
275
281
  File.open(log_file, "a") do |f|
276
282
  f.puts "Error: #{e.message}"
@@ -298,7 +304,7 @@ module ClaudeSwarm
298
304
 
299
305
  # Execute the command and capture output
300
306
  begin
301
- puts "Debug: Executing after command #{index + 1}/#{commands.size}: #{command}" if @debug && !@prompt
307
+ puts "Debug: Executing after command #{index + 1}/#{commands.size}: #{command}" if @debug && !@non_interactive_prompt
302
308
 
303
309
  # Use system with output capture
304
310
  output = %x(#{command} 2>&1)
@@ -315,18 +321,18 @@ module ClaudeSwarm
315
321
  end
316
322
 
317
323
  # Show output if in debug mode or if command failed
318
- if (@debug || !success) && !@prompt
324
+ if (@debug || !success) && !@non_interactive_prompt
319
325
  puts "After command #{index + 1} output:"
320
326
  puts output
321
327
  puts "Exit status: #{$CHILD_STATUS.exitstatus}"
322
328
  end
323
329
 
324
330
  unless success
325
- puts "❌ After command #{index + 1} failed: #{command}" unless @prompt
331
+ puts "❌ After command #{index + 1} failed: #{command}" unless @non_interactive_prompt
326
332
  all_succeeded = false
327
333
  end
328
334
  rescue StandardError => e
329
- puts "Error executing after command #{index + 1}: #{e.message}" unless @prompt
335
+ puts "Error executing after command #{index + 1}: #{e.message}" unless @non_interactive_prompt
330
336
  if @session_path
331
337
  File.open(log_file, "a") do |f|
332
338
  f.puts "Error: #{e.message}"
@@ -374,7 +380,7 @@ module ClaudeSwarm
374
380
  # Execute after commands if configured
375
381
  main_instance = @config.main_instance_config
376
382
  after_commands = @config.after_commands
377
- if after_commands.any? && !@restore_session_path && !@prompt
383
+ if after_commands.any? && !@restore_session_path && !@non_interactive_prompt
378
384
  Dir.chdir(main_instance[:directory]) do
379
385
  puts
380
386
  puts "⚙️ Executing after commands..."
@@ -438,7 +444,7 @@ module ClaudeSwarm
438
444
 
439
445
  File.write(metadata_file, JSON.pretty_generate(metadata))
440
446
  rescue StandardError => e
441
- puts "⚠️ Error updating session metadata: #{e.message}" unless @prompt
447
+ puts "⚠️ Error updating session metadata: #{e.message}" unless @non_interactive_prompt
442
448
  end
443
449
 
444
450
  def calculate_total_cost
@@ -487,7 +493,7 @@ module ClaudeSwarm
487
493
  File.symlink(@session_path, symlink_path)
488
494
  rescue StandardError => e
489
495
  # Don't fail the process if symlink creation fails
490
- puts "⚠️ Warning: Could not create run symlink: #{e.message}" unless @prompt
496
+ puts "⚠️ Warning: Could not create run symlink: #{e.message}" unless @non_interactive_prompt
491
497
  end
492
498
 
493
499
  def cleanup_run_symlink
@@ -589,6 +595,7 @@ module ClaudeSwarm
589
595
  end
590
596
  end
591
597
 
598
+ # Always add instance prompt if it exists
592
599
  if instance[:prompt]
593
600
  parts << "--append-system-prompt"
594
601
  parts << instance[:prompt]
@@ -608,12 +615,18 @@ module ClaudeSwarm
608
615
  parts << "--mcp-config"
609
616
  parts << mcp_config_path
610
617
 
611
- if @prompt
618
+ # Handle different modes
619
+ if @non_interactive_prompt
620
+ # Non-interactive mode with -p
612
621
  parts << "-p"
613
- parts << @prompt
614
- else
615
- parts << "#{instance[:prompt]}\n\nNow just say 'I am ready to start'"
622
+ parts << @non_interactive_prompt
623
+ elsif @interactive_prompt
624
+ # Interactive mode with initial prompt (no -p flag)
625
+ parts << @interactive_prompt
616
626
  end
627
+ # else: Interactive mode without initial prompt - nothing to add
628
+
629
+ parts
617
630
  end
618
631
 
619
632
  def restore_worktrees_if_needed(session_path)
@@ -624,7 +637,7 @@ module ClaudeSwarm
624
637
  worktree_data = metadata["worktree"]
625
638
  return unless worktree_data && worktree_data["enabled"]
626
639
 
627
- unless @prompt
640
+ unless @non_interactive_prompt
628
641
  puts "🌳 Restoring Git worktrees..."
629
642
  puts
630
643
  end
@@ -638,7 +651,7 @@ module ClaudeSwarm
638
651
  all_instances = @config.instances.values
639
652
  @worktree_manager.setup_worktrees(all_instances)
640
653
 
641
- return if @prompt
654
+ return if @non_interactive_prompt
642
655
 
643
656
  puts "✓ Worktrees restored with branch: #{@worktree_manager.worktree_name}"
644
657
  puts
@@ -12,12 +12,20 @@ module ClaudeSwarm
12
12
  optional(:new_session).filled(:bool).description("Start a new session (default: false)")
13
13
  optional(:system_prompt).filled(:string).description("Override the system prompt for this request")
14
14
  optional(:description).filled(:string).description("A description for the request")
15
+ optional(:thinking_budget).filled(:string).description("Thinking budget: \"think\" < \"think hard\" < \"think harder\" < \"ultrathink\". Each level increases Claude's thinking allocation. Auto-select based on task complexity.")
15
16
  end
16
17
 
17
- def call(prompt:, new_session: false, system_prompt: nil, description: nil)
18
+ def call(prompt:, new_session: false, system_prompt: nil, description: nil, thinking_budget: nil)
18
19
  executor = ClaudeMcpServer.executor
19
20
  instance_config = ClaudeMcpServer.instance_config
20
21
 
22
+ # Prepend thinking budget to prompt if provided
23
+ final_prompt = if thinking_budget
24
+ "#{thinking_budget}: #{prompt}"
25
+ else
26
+ prompt
27
+ end
28
+
21
29
  options = {
22
30
  new_session: new_session,
23
31
  system_prompt: system_prompt || instance_config[:prompt],
@@ -33,7 +41,7 @@ module ClaudeSwarm
33
41
  # Add connections from instance config
34
42
  options[:connections] = instance_config[:connections] if instance_config[:connections]&.any?
35
43
 
36
- response = executor.execute(prompt, options)
44
+ response = executor.execute(final_prompt, options)
37
45
 
38
46
  # Return just the result text as expected by MCP
39
47
  response["result"]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeSwarm
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: claude_swarm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Arruda