claude_swarm 0.1.1 → 0.1.3

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: 868bb8a62ddb042e9971915da978ef619051be6ecdee7fb8b9a40678e11e2930
4
- data.tar.gz: d8fdc4cbb9c6336a33d6e377b332a7e85511f594491aa1691f54a12ce76748f6
3
+ metadata.gz: 8d82adc5c9d855904f9809e4e2c8d9e34b7dbd5b2dd6ebec98e4072d6c81f3cf
4
+ data.tar.gz: 1faa01211ebe1b05fcc0dbf8b654b77e3512b4a4653b9169ef8c1b1e40226680
5
5
  SHA512:
6
- metadata.gz: f07b9ebdd1378f816d659d26ce68751fe9afd5ef79f98bc025d42e1a7fe88686ec7ca9b4bcd108047dc9e3d1a96b10446693a15f22ade0855c83a465278a5a0b
7
- data.tar.gz: b43959ad4effb4c9bcfdd2cec0ac955b02f5303941ae313e0751f7dafb7a7a84ce86bb15d9e08b09cfc857329086473c4f6dcc0a5ebe3b87741721b9cb64997f
6
+ metadata.gz: 539f27f040ff91900bd8d88f925564f07ee4ad673eaaadcfdda228e44ca21adfd46a8be3dd40ae3a9b267b15ac5df4a5bd154d855f75ef85f1888522b71a716b
7
+ data.tar.gz: 81c0d46bf825684597d80271e7b0e7480b8c92e80c2465af1513b6d80f4dea2100b173261311243f5514e4aeb07bbdedebc0467b293415647bd8397161f98e8d
data/CHANGELOG.md CHANGED
@@ -1,4 +1,21 @@
1
- ## [Unreleased]
1
+ ## [0.1.3]
2
+
3
+ ### Fixed
4
+ - Fixed duplicate prompt arguments being passed to Claude Code executor, which could cause command execution failures
5
+
6
+ ### Changed
7
+ - Improved logging to track request flow between instances using `from_instance` and `to_instance` fields instead of generic `instance_name`
8
+ - Added required `calling_instance` parameter to MCP server command to properly identify the source of requests in tree configurations
9
+ - Consolidated session files into a single directory structure (`.claude-swarm/sessions/<timestamp>/`)
10
+ - MCP configuration files are now stored alongside session logs in the same timestamped directory
11
+ - Session logs are now named `session.log` instead of `session_<timestamp>.log`
12
+ - Improved organization by keeping all session-related files together
13
+
14
+ ## [0.1.2] - 2025-05-29
15
+
16
+ ### Added
17
+ - Added `-p` / `--prompt` flag to pass prompts directly to the main Claude instance for non-interactive mode
18
+ - Output suppression when running with the `-p` flag for cleaner scripted usage
2
19
 
3
20
  ## [0.1.1] - 2025-05-24
4
21
 
data/README.md CHANGED
@@ -77,7 +77,7 @@ claude-swarm --vibe # That will allow ALL tools for all instances! Be Careful!
77
77
  This will:
78
78
  - Launch the main instance (lead) with connections to other instances
79
79
  - The lead instance can communicate with the other instances via MCP
80
- - All instances log to `.claude-swarm/logs/session-{timestamp}/`
80
+ - All session files are stored in `.claude-swarm/sessions/{timestamp}/`
81
81
 
82
82
  #### Multi-Level Swarm Example
83
83
 
@@ -374,6 +374,10 @@ claude-swarm -c team-config.yml
374
374
  # Run with --dangerously-skip-permissions for all instances
375
375
  claude-swarm --vibe
376
376
 
377
+ # Run in non-interactive mode with a prompt
378
+ claude-swarm -p "Implement the new user authentication feature"
379
+ claude-swarm --prompt "Fix the bug in the payment module"
380
+
377
381
  # Show version
378
382
  claude-swarm version
379
383
 
@@ -396,7 +400,9 @@ claude-swarm mcp-serve INSTANCE_NAME --config CONFIG_FILE --session-timestamp TI
396
400
  - **task**: Execute tasks using Claude Code with configurable tools and return results
397
401
  - **session_info**: Get current Claude session information including ID and working directory
398
402
  - **reset_session**: Reset the Claude session for a fresh start
399
- 6. **Centralized Logging**: All instances log to `.claude-swarm/logs/session-{timestamp}/` with detailed request/response tracking
403
+ 6. **Session Management**: All session files are organized in `.claude-swarm/sessions/{timestamp}/`:
404
+ - MCP configuration files: `{instance_name}.mcp.json`
405
+ - Session log: `session.log` with detailed request/response tracking
400
406
 
401
407
  ## Troubleshooting
402
408
 
@@ -419,16 +425,16 @@ claude-swarm mcp-serve INSTANCE_NAME --config CONFIG_FILE --session-timestamp TI
419
425
  ### Debug Output
420
426
 
421
427
  The swarm will display:
422
- - Generated MCP configuration location
428
+ - Session directory location (`.claude-swarm/sessions/{timestamp}/`)
423
429
  - Main instance details (model, directory, tools, connections)
424
430
  - The exact command being run
425
431
 
426
- ### Logs
432
+ ### Session Files
427
433
 
428
- Check the centralized logs in `.claude-swarm/logs/session-{timestamp}/` for:
429
- - Individual instance logs
430
- - MCP server communication
431
- - Error messages and debugging information
434
+ Check the session directory `.claude-swarm/sessions/{timestamp}/` for:
435
+ - `session.log`: Detailed logs with request/response tracking
436
+ - `{instance}.mcp.json`: MCP configuration for each instance
437
+ - All files for a session are kept together for easy review
432
438
 
433
439
  ## Architecture
434
440
 
data/claude-swarm.yml ADDED
@@ -0,0 +1,26 @@
1
+ version: 1
2
+ swarm:
3
+ name: "Swarm Name"
4
+ main: lead_developer
5
+ instances:
6
+ lead_developer:
7
+ directory: .
8
+ model: sonnet
9
+ prompt: "You are the lead developer coordinating the team"
10
+ tools: [Read, Edit, Bash, Write]
11
+ connections: [frontend_dev]
12
+
13
+ # Example instances (uncomment and modify as needed):
14
+
15
+ frontend_dev:
16
+ directory: .
17
+ model: sonnet
18
+ prompt: "You specialize in frontend development with React, TypeScript, and modern web technologies"
19
+ tools: [Read, Edit, Write, "Bash(npm:*)", "Bash(yarn:*)", "Bash(pnpm:*)"]
20
+ connections: [css_dev]
21
+
22
+ css_dev:
23
+ directory: .
24
+ model: sonnet
25
+ prompt: "You specialize in CSS development with Tailwind CSS, CSS Modules, and modern web technologies"
26
+ tools: [Read, Edit, Write, "Bash(npm:*)", "Bash(yarn:*)", "Bash(pnpm:*)"]
@@ -67,8 +67,8 @@ module ClaudeSwarm
67
67
  # Always use JSON output format for structured responses
68
68
  cmd_array += ["--output-format", "json"]
69
69
 
70
- # Add non-interactive mode
71
- cmd_array << "--print"
70
+ # Add non-interactive mode with prompt
71
+ cmd_array += ["--print", "-p", prompt]
72
72
 
73
73
  # Add any custom system prompt
74
74
  cmd_array += ["--system-prompt", options[:system_prompt]] if options[:system_prompt]
@@ -81,9 +81,6 @@ module ClaudeSwarm
81
81
  cmd_array += ["--allowedTools", tools]
82
82
  end
83
83
 
84
- # Add the prompt as the last argument (no escaping needed with array syntax)
85
- cmd_array << prompt
86
-
87
84
  cmd_array
88
85
  end
89
86
 
@@ -9,15 +9,16 @@ require_relative "claude_code_executor"
9
9
  module ClaudeSwarm
10
10
  class ClaudeMcpServer
11
11
  SWARM_DIR = ".claude-swarm"
12
- LOGS_DIR = "logs"
12
+ SESSIONS_DIR = "sessions"
13
13
 
14
14
  # Class variables to share state with tool classes
15
15
  class << self
16
- attr_accessor :executor, :instance_config, :logger, :session_timestamp
16
+ attr_accessor :executor, :instance_config, :logger, :session_timestamp, :calling_instance
17
17
  end
18
18
 
19
- def initialize(instance_config)
19
+ def initialize(instance_config, calling_instance:)
20
20
  @instance_config = instance_config
21
+ @calling_instance = calling_instance
21
22
  @executor = ClaudeCodeExecutor.new(
22
23
  working_directory: instance_config[:directory],
23
24
  model: instance_config[:model],
@@ -32,6 +33,7 @@ module ClaudeSwarm
32
33
  self.class.executor = @executor
33
34
  self.class.instance_config = @instance_config
34
35
  self.class.logger = @logger
36
+ self.class.calling_instance = @calling_instance
35
37
  end
36
38
 
37
39
  private
@@ -41,13 +43,13 @@ module ClaudeSwarm
41
43
  # Otherwise create a new timestamp
42
44
  self.class.session_timestamp ||= ENV["CLAUDE_SWARM_SESSION_TIMESTAMP"] || Time.now.strftime("%Y%m%d_%H%M%S")
43
45
 
44
- # Ensure the logs directory exists
45
- logs_dir = File.join(Dir.pwd, SWARM_DIR, LOGS_DIR)
46
- FileUtils.mkdir_p(logs_dir)
46
+ # Ensure the session directory exists
47
+ session_dir = File.join(Dir.pwd, SWARM_DIR, SESSIONS_DIR, self.class.session_timestamp)
48
+ FileUtils.mkdir_p(session_dir)
47
49
 
48
- # Create logger with timestamped filename
49
- log_filename = "session_#{self.class.session_timestamp}.log"
50
- log_path = File.join(logs_dir, log_filename)
50
+ # Create logger with session.log filename
51
+ log_filename = "session.log"
52
+ log_path = File.join(session_dir, log_filename)
51
53
  @logger = Logger.new(log_path)
52
54
  @logger.level = Logger::INFO
53
55
 
@@ -103,7 +105,8 @@ module ClaudeSwarm
103
105
  # Log the request
104
106
  log_entry = {
105
107
  timestamp: Time.now.utc.iso8601,
106
- instance_name: instance_config[:name],
108
+ from_instance: ClaudeMcpServer.calling_instance, # The instance making the request
109
+ to_instance: instance_config[:name], # This instance is receiving the request
107
110
  model: instance_config[:model],
108
111
  working_directory: instance_config[:directory],
109
112
  session_id: executor.session_id,
@@ -120,7 +123,10 @@ module ClaudeSwarm
120
123
  response = executor.execute(prompt, options)
121
124
 
122
125
  # Log the response
123
- response_entry = log_entry.merge(
126
+ response_entry = {
127
+ timestamp: Time.now.utc.iso8601,
128
+ from_instance: instance_config[:name], # This instance is sending the response
129
+ to_instance: ClaudeMcpServer.calling_instance, # The instance that made the request receives the response
124
130
  session_id: executor.session_id, # Update with new session ID if changed
125
131
  response: {
126
132
  result: response["result"],
@@ -129,7 +135,7 @@ module ClaudeSwarm
129
135
  is_error: response["is_error"],
130
136
  total_cost: response["total_cost"]
131
137
  }
132
- )
138
+ }
133
139
 
134
140
  logger.info("RESPONSE: #{JSON.pretty_generate(response_entry)}")
135
141
 
@@ -17,6 +17,8 @@ module ClaudeSwarm
17
17
  desc: "Path to configuration file"
18
18
  method_option :vibe, type: :boolean, default: false,
19
19
  desc: "Run with --dangerously-skip-permissions for all instances"
20
+ method_option :prompt, aliases: "-p", type: :string,
21
+ desc: "Prompt to pass to the main Claude instance (non-interactive mode)"
20
22
  def start(config_file = nil)
21
23
  config_path = config_file || options[:config]
22
24
  unless File.exist?(config_path)
@@ -24,11 +26,15 @@ module ClaudeSwarm
24
26
  exit 1
25
27
  end
26
28
 
27
- say "Starting Claude Swarm from #{config_path}..."
29
+ say "Starting Claude Swarm from #{config_path}..." unless options[:prompt]
28
30
  begin
29
31
  config = Configuration.new(config_path)
30
- generator = McpGenerator.new(config, vibe: options[:vibe])
31
- orchestrator = Orchestrator.new(config, generator, vibe: options[:vibe])
32
+ session_timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
33
+ generator = McpGenerator.new(config, vibe: options[:vibe], timestamp: session_timestamp)
34
+ orchestrator = Orchestrator.new(config, generator,
35
+ vibe: options[:vibe],
36
+ prompt: options[:prompt],
37
+ session_timestamp: session_timestamp)
32
38
  orchestrator.start
33
39
  rescue Error => e
34
40
  error e.message
@@ -57,6 +63,8 @@ module ClaudeSwarm
57
63
  desc: "Enable debug output"
58
64
  method_option :vibe, type: :boolean, default: false,
59
65
  desc: "Run with --dangerously-skip-permissions"
66
+ method_option :calling_instance, type: :string, required: true,
67
+ desc: "Name of the instance that launched this MCP server"
60
68
  def mcp_serve
61
69
  instance_config = {
62
70
  name: options[:name],
@@ -69,7 +77,7 @@ module ClaudeSwarm
69
77
  }
70
78
 
71
79
  begin
72
- server = ClaudeMcpServer.new(instance_config)
80
+ server = ClaudeMcpServer.new(instance_config, calling_instance: options[:calling_instance])
73
81
  server.start
74
82
  rescue StandardError => e
75
83
  error "Error starting MCP server: #{e.message}"
@@ -7,10 +7,12 @@ require "shellwords"
7
7
  module ClaudeSwarm
8
8
  class McpGenerator
9
9
  SWARM_DIR = ".claude-swarm"
10
+ SESSIONS_SUBDIR = "sessions"
10
11
 
11
- def initialize(configuration, vibe: false)
12
+ def initialize(configuration, vibe: false, timestamp: nil)
12
13
  @config = configuration
13
14
  @vibe = vibe
15
+ @timestamp = timestamp || Time.now.strftime("%Y%m%d_%H%M%S")
14
16
  end
15
17
 
16
18
  def generate_all
@@ -22,7 +24,7 @@ module ClaudeSwarm
22
24
  end
23
25
 
24
26
  def mcp_config_path(instance_name)
25
- File.join(Dir.pwd, SWARM_DIR, "#{instance_name}.mcp.json")
27
+ File.join(Dir.pwd, SWARM_DIR, SESSIONS_SUBDIR, @timestamp, "#{instance_name}.mcp.json")
26
28
  end
27
29
 
28
30
  private
@@ -34,9 +36,9 @@ module ClaudeSwarm
34
36
  def ensure_swarm_directory
35
37
  FileUtils.mkdir_p(swarm_dir)
36
38
 
37
- # Create logs directory as well
38
- logs_dir = File.join(swarm_dir, "logs")
39
- FileUtils.mkdir_p(logs_dir)
39
+ # Create session directory with timestamp
40
+ session_dir = File.join(swarm_dir, SESSIONS_SUBDIR, @timestamp)
41
+ FileUtils.mkdir_p(session_dir)
40
42
 
41
43
  gitignore_path = File.join(swarm_dir, ".gitignore")
42
44
  File.write(gitignore_path, "*\n") unless File.exist?(gitignore_path)
@@ -53,7 +55,7 @@ module ClaudeSwarm
53
55
  # Add connection MCPs for other instances
54
56
  instance[:connections].each do |connection_name|
55
57
  connected_instance = @config.instances[connection_name]
56
- mcp_servers[connection_name] = build_instance_mcp_config(connection_name, connected_instance)
58
+ mcp_servers[connection_name] = build_instance_mcp_config(connection_name, connected_instance, calling_instance: name)
57
59
  end
58
60
 
59
61
  config = {
@@ -81,7 +83,7 @@ module ClaudeSwarm
81
83
  end
82
84
  end
83
85
 
84
- def build_instance_mcp_config(name, instance)
86
+ def build_instance_mcp_config(name, instance, calling_instance:)
85
87
  # Get the path to the claude-swarm executable
86
88
  exe_path = "claude-swarm"
87
89
 
@@ -100,6 +102,8 @@ module ClaudeSwarm
100
102
 
101
103
  args.push("--mcp-config-path", mcp_config_path(name))
102
104
 
105
+ args.push("--calling-instance", calling_instance) if calling_instance
106
+
103
107
  args.push("--vibe") if @vibe
104
108
 
105
109
  {
@@ -4,39 +4,48 @@ require "shellwords"
4
4
 
5
5
  module ClaudeSwarm
6
6
  class Orchestrator
7
- def initialize(configuration, mcp_generator, vibe: false)
7
+ def initialize(configuration, mcp_generator, vibe: false, prompt: nil, session_timestamp: nil)
8
8
  @config = configuration
9
9
  @generator = mcp_generator
10
10
  @vibe = vibe
11
+ @prompt = prompt
12
+ @session_timestamp = session_timestamp || Time.now.strftime("%Y%m%d_%H%M%S")
11
13
  end
12
14
 
13
15
  def start
14
- puts "🐝 Starting Claude Swarm: #{@config.swarm_name}"
15
- puts "😎 Vibe mode ON" if @vibe
16
- puts
16
+ unless @prompt
17
+ puts "🐝 Starting Claude Swarm: #{@config.swarm_name}"
18
+ puts "😎 Vibe mode ON" if @vibe
19
+ puts
20
+ end
17
21
 
18
- # Set session timestamp for all instances to share the same log file
19
- session_timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
20
- ENV["CLAUDE_SWARM_SESSION_TIMESTAMP"] = session_timestamp
21
- puts "📝 Session logs will be saved to: .claude-swarm/logs/session_#{session_timestamp}.log"
22
- puts
22
+ # Set session timestamp for all instances to share the same session directory
23
+ ENV["CLAUDE_SWARM_SESSION_TIMESTAMP"] = @session_timestamp
24
+ unless @prompt
25
+ puts "📝 Session files will be saved to: .claude-swarm/sessions/#{@session_timestamp}/"
26
+ puts
27
+ end
23
28
 
24
29
  # Generate all MCP configuration files
25
30
  @generator.generate_all
26
- puts "✓ Generated MCP configurations in .claude-swarm/"
27
- puts
31
+ unless @prompt
32
+ puts "✓ Generated MCP configurations in session directory"
33
+ puts
34
+ end
28
35
 
29
36
  # Launch the main instance
30
37
  main_instance = @config.main_instance_config
31
- puts "🚀 Launching main instance: #{@config.main_instance}"
32
- puts " Model: #{main_instance[:model]}"
33
- puts " Directory: #{main_instance[:directory]}"
34
- puts " Tools: #{main_instance[:tools].join(", ")}" if main_instance[:tools].any?
35
- puts " Connections: #{main_instance[:connections].join(", ")}" if main_instance[:connections].any?
36
- puts
38
+ unless @prompt
39
+ puts "🚀 Launching main instance: #{@config.main_instance}"
40
+ puts " Model: #{main_instance[:model]}"
41
+ puts " Directory: #{main_instance[:directory]}"
42
+ puts " Tools: #{main_instance[:tools].join(", ")}" if main_instance[:tools].any?
43
+ puts " Connections: #{main_instance[:connections].join(", ")}" if main_instance[:connections].any?
44
+ puts
45
+ end
37
46
 
38
47
  command = build_main_command(main_instance)
39
- if ENV["DEBUG"]
48
+ if ENV["DEBUG"] && !@prompt
40
49
  puts "Running: #{command}"
41
50
  puts
42
51
  end
@@ -65,6 +74,8 @@ module ClaudeSwarm
65
74
  mcp_config_path = @generator.mcp_config_path(@config.main_instance)
66
75
  parts << "--mcp-config #{mcp_config_path}"
67
76
 
77
+ parts << "-p #{Shellwords.escape(@prompt)}" if @prompt
78
+
68
79
  parts.join(" ")
69
80
  end
70
81
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeSwarm
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.3"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: claude_swarm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Arruda
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-05-28 00:00:00.000000000 Z
10
+ date: 2025-05-30 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: thor
@@ -57,6 +57,7 @@ files:
57
57
  - LICENSE
58
58
  - README.md
59
59
  - Rakefile
60
+ - claude-swarm.yml
60
61
  - example/claude-swarm.yml
61
62
  - exe/claude-swarm
62
63
  - lib/claude_swarm.rb