claude_swarm 0.1.4 → 0.1.6

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: a4597e6fdc67837e39ae1c175cc9dded454460ec00488d92aa81cab928737b4a
4
- data.tar.gz: 46790b958dc4916a3df066ae7384e753c84c6c4cc624ac62754cab646f2c9102
3
+ metadata.gz: 32206b2e4ec4228e8d9c2450447f0508a2c58378d37c80ce24d327a54653ee05
4
+ data.tar.gz: c11bf2d8042b73de10ee2f40c01e977d8bae0a8bd74e23b74fc29216735396c3
5
5
  SHA512:
6
- metadata.gz: f876006a8368881b252e675fab2b2999ae5a79574caff9db081ea7ea72402ce172524d7c6a2fe9c4b967502971a1cab83895dfd1f339592a516c58298450426a
7
- data.tar.gz: b55120d481b8605e11a7024f1d6626f5c1fecca18627b6247e4b517c16e29d762166408a1753b1fc1b845c80f820f9701ce30ef2b0edb7e8a58da1b3635d0cc4
6
+ metadata.gz: 5e639606be661128f4c23411f6e9c4164cf9b212ce37e6f99178ba33e4d67a5eb0995b25c1dbe885fff0c20230ea12e347c7b04c7b3bef8b4313353bce6cb948
7
+ data.tar.gz: 8d51962bb83f7268510b44a1aff1b13c5f5260c008b8bb26b41d971d0fbdd816c76a9221c275c028851c7ccc825c2aeee7b0d7d0e311f50485119693b3b5791a
data/.rubocop.yml CHANGED
@@ -59,4 +59,7 @@ Metrics/ModuleLength:
59
59
  Enabled: false
60
60
 
61
61
  Minitest/MultipleAssertions:
62
+ Enabled: false
63
+
64
+ Metrics/ParameterLists:
62
65
  Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## [0.1.6]
2
+ - Refactor: move tools out of the ClaudeMcpServer class
3
+ - Move logging into code executor and save instance interaction streams to session.log
4
+ - Human readable logs with thoughts and tool calls
5
+
6
+ ## [0.1.5]
7
+
8
+ ### Changed
9
+ - **Improved command execution**: Switched from `exec` to `Dir.chdir` + `system` for better process handling and proper directory context
10
+ - Command arguments are now passed as an array instead of a shell string, eliminating the need for manual shell escaping
11
+ - Added default prompt behavior: when no `-p` flag is provided, a default prompt is added to help Claude understand it should start working
12
+
13
+ ### Internal
14
+ - Updated test suite to match new command execution implementation
15
+ - Removed shellwords escaping tests as they're no longer needed with array-based command execution
16
+
1
17
  ## [0.1.4]
2
18
 
3
19
  ### Added
data/README.md CHANGED
@@ -52,7 +52,7 @@ swarm:
52
52
  frontend:
53
53
  description: "Frontend specialist handling UI and user experience"
54
54
  directory: ./frontend
55
- model: sonnet
55
+ model: opus
56
56
  tools:
57
57
  - Edit
58
58
  - Write
@@ -60,7 +60,7 @@ swarm:
60
60
  backend:
61
61
  description: "Backend developer managing APIs and data layer"
62
62
  directory: ./backend
63
- model: sonnet
63
+ model: opus
64
64
  tools:
65
65
  - Edit
66
66
  - Write
@@ -111,14 +111,14 @@ swarm:
111
111
  react_dev:
112
112
  description: "React developer specializing in components and state management"
113
113
  directory: ./web-frontend/src
114
- model: sonnet
114
+ model: opus
115
115
  prompt: "You specialize in React components and state management"
116
116
  tools: [Edit, Write, "Bash(npm:*)"]
117
117
 
118
118
  css_expert:
119
119
  description: "CSS specialist handling styling and responsive design"
120
120
  directory: ./web-frontend/styles
121
- model: sonnet
121
+ model: opus
122
122
  prompt: "You handle all CSS and styling concerns"
123
123
  tools: [Edit, Write, Read]
124
124
 
@@ -133,21 +133,21 @@ swarm:
133
133
  api_dev:
134
134
  description: "API developer building REST endpoints"
135
135
  directory: ./api-server/src
136
- model: sonnet
136
+ model: opus
137
137
  prompt: "You develop REST API endpoints"
138
138
  tools: [Edit, Write, Bash]
139
139
 
140
140
  database_expert:
141
141
  description: "Database specialist managing schemas and migrations"
142
142
  directory: ./api-server/db
143
- model: sonnet
143
+ model: opus
144
144
  prompt: "You handle database schema and migrations"
145
145
  tools: [Edit, Write, "Bash(psql:*, migrate:*)"]
146
146
 
147
147
  mobile_lead:
148
148
  description: "Mobile team lead coordinating cross-platform development"
149
149
  directory: ./mobile-app
150
- model: sonnet
150
+ model: opus
151
151
  connections: [ios_dev, android_dev]
152
152
  prompt: "You coordinate mobile development across platforms"
153
153
  tools: [Read, Edit]
@@ -155,21 +155,21 @@ swarm:
155
155
  ios_dev:
156
156
  description: "iOS developer building native Apple applications"
157
157
  directory: ./mobile-app/ios
158
- model: sonnet
158
+ model: opus
159
159
  prompt: "You develop the iOS application"
160
160
  tools: [Edit, Write, "Bash(xcodebuild:*, pod:*)"]
161
161
 
162
162
  android_dev:
163
163
  description: "Android developer creating native Android apps"
164
164
  directory: ./mobile-app/android
165
- model: sonnet
165
+ model: opus
166
166
  prompt: "You develop the Android application"
167
167
  tools: [Edit, Write, "Bash(gradle:*, adb:*)"]
168
168
 
169
169
  devops:
170
170
  description: "DevOps engineer managing CI/CD and infrastructure"
171
171
  directory: ./infrastructure
172
- model: sonnet
172
+ model: opus
173
173
  prompt: "You handle CI/CD and infrastructure"
174
174
  tools: [Read, Edit, "Bash(docker:*, kubectl:*)"]
175
175
  ```
@@ -305,7 +305,7 @@ swarm:
305
305
  frontend:
306
306
  description: "Frontend developer specializing in React and TypeScript"
307
307
  directory: ./frontend
308
- model: sonnet
308
+ model: opus
309
309
  connections: [architect]
310
310
  prompt: "You specialize in React, TypeScript, and modern frontend development"
311
311
  tools:
@@ -316,7 +316,7 @@ swarm:
316
316
  backend:
317
317
  description: "Backend developer building APIs and services"
318
318
  directory: ./backend
319
- model: sonnet
319
+ model: opus
320
320
  connections: [architect, database]
321
321
  tools:
322
322
  - Edit
@@ -334,7 +334,7 @@ swarm:
334
334
  devops:
335
335
  description: "DevOps engineer handling deployment and infrastructure"
336
336
  directory: .
337
- model: sonnet
337
+ model: opus
338
338
  connections: [architect]
339
339
  tools:
340
340
  - Read
@@ -367,7 +367,7 @@ swarm:
367
367
  data_analyst:
368
368
  description: "Data analyst processing research data and statistics"
369
369
  directory: ~/research/data
370
- model: sonnet
370
+ model: opus
371
371
  tools:
372
372
  - Read
373
373
  - Write
@@ -381,7 +381,7 @@ swarm:
381
381
  writer:
382
382
  description: "Technical writer preparing research documentation"
383
383
  directory: ~/research/papers
384
- model: sonnet
384
+ model: opus
385
385
  tools:
386
386
  - Edit
387
387
  - Write
data/claude-swarm.yml CHANGED
@@ -6,7 +6,7 @@ swarm:
6
6
  lead_developer:
7
7
  description: "Lead developer coordinating the team and making architectural decisions"
8
8
  directory: .
9
- model: sonnet
9
+ model: opus
10
10
  prompt: "You are the lead developer coordinating the team"
11
11
  tools: [Read, Edit, Bash, Write]
12
12
  connections: [frontend_dev]
@@ -16,6 +16,6 @@ swarm:
16
16
  frontend_dev:
17
17
  description: "Frontend developer specializing in React and modern web technologies"
18
18
  directory: .
19
- model: sonnet
19
+ model: opus
20
20
  prompt: "You specialize in frontend development with React, TypeScript, and modern web technologies"
21
21
  tools: [Read, Edit, Write, "Bash(npm:*)", "Bash(yarn:*)", "Bash(pnpm:*)"]
@@ -2,43 +2,85 @@
2
2
 
3
3
  require "json"
4
4
  require "open3"
5
+ require "logger"
6
+ require "fileutils"
5
7
 
6
8
  module ClaudeSwarm
7
9
  class ClaudeCodeExecutor
8
- attr_reader :session_id, :last_response, :working_directory
10
+ SWARM_DIR = ".claude-swarm"
11
+ SESSIONS_DIR = "sessions"
9
12
 
10
- def initialize(working_directory: Dir.pwd, model: nil, mcp_config: nil, vibe: false)
13
+ attr_reader :session_id, :last_response, :working_directory, :logger, :session_timestamp
14
+
15
+ def initialize(working_directory: Dir.pwd, model: nil, mcp_config: nil, vibe: false, instance_name: nil, calling_instance: nil)
11
16
  @working_directory = working_directory
12
17
  @model = model
13
18
  @mcp_config = mcp_config
14
19
  @vibe = vibe
15
20
  @session_id = nil
16
21
  @last_response = nil
22
+ @instance_name = instance_name
23
+ @calling_instance = calling_instance
24
+
25
+ # Setup logging
26
+ setup_logging
17
27
  end
18
28
 
19
29
  def execute(prompt, options = {})
20
- cmd_array = build_command_array(prompt, options)
21
-
22
- stdout, stderr, status = Open3.capture3(*cmd_array, chdir: @working_directory)
23
-
24
- raise ExecutionError, "Claude Code execution failed: #{stderr}" unless status.success?
30
+ # Log the request
31
+ log_request(prompt)
25
32
 
26
- begin
27
- response = JSON.parse(stdout)
28
- @last_response = response
29
-
30
- # Extract and store session ID from the response
31
- @session_id = response["session_id"]
33
+ cmd_array = build_command_array(prompt, options)
32
34
 
33
- response
34
- rescue JSON::ParserError => e
35
- raise ParseError, "Failed to parse JSON response: #{e.message}\nOutput: #{stdout}"
35
+ # Variables to collect output
36
+ stderr_output = []
37
+ result_response = nil
38
+
39
+ # Execute command with streaming
40
+ Open3.popen3(*cmd_array, chdir: @working_directory) do |stdin, stdout, stderr, wait_thread|
41
+ stdin.close
42
+
43
+ # Read stderr in a separate thread
44
+ stderr_thread = Thread.new do
45
+ stderr.each_line { |line| stderr_output << line }
46
+ end
47
+
48
+ # Process stdout line by line
49
+ stdout.each_line do |line|
50
+ json_data = JSON.parse(line.strip)
51
+
52
+ # Log each JSON event
53
+ log_streaming_event(json_data)
54
+
55
+ # Capture session_id from system init
56
+ @session_id = json_data["session_id"] if json_data["type"] == "system" && json_data["subtype"] == "init"
57
+
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
63
+
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}"
73
+ end
36
74
  end
37
- end
38
75
 
39
- def execute_text(prompt, options = {})
40
- response = execute(prompt, options)
41
- response["result"] || ""
76
+ # Ensure we got a result
77
+ raise ParseError, "No result found in stream output" unless result_response
78
+
79
+ result_response
80
+ rescue StandardError => e
81
+ @logger.error("Unexpected error for #{@instance_name}: #{e.class} - #{e.message}")
82
+ @logger.error("Backtrace: #{e.backtrace.join("\n")}")
83
+ raise
42
84
  end
43
85
 
44
86
  def reset_session
@@ -52,12 +94,90 @@ module ClaudeSwarm
52
94
 
53
95
  private
54
96
 
97
+ def setup_logging
98
+ # Use environment variable for session timestamp if available (set by orchestrator)
99
+ # Otherwise create a new timestamp
100
+ @session_timestamp = ENV["CLAUDE_SWARM_SESSION_TIMESTAMP"] || Time.now.strftime("%Y%m%d_%H%M%S")
101
+
102
+ # Ensure the session directory exists
103
+ session_dir = File.join(Dir.pwd, SWARM_DIR, SESSIONS_DIR, @session_timestamp)
104
+ FileUtils.mkdir_p(session_dir)
105
+
106
+ # Create logger with session.log filename
107
+ log_filename = "session.log"
108
+ log_path = File.join(session_dir, log_filename)
109
+ @logger = Logger.new(log_path)
110
+ @logger.level = Logger::INFO
111
+
112
+ # Custom formatter for better readability
113
+ @logger.formatter = proc do |severity, datetime, _progname, msg|
114
+ "[#{datetime.strftime("%Y-%m-%d %H:%M:%S.%L")}] [#{severity}] #{msg}\n"
115
+ end
116
+
117
+ @logger.info("Started Claude Code executor for instance: #{@instance_name}") if @instance_name
118
+ end
119
+
120
+ def log_request(prompt)
121
+ @logger.info("#{@calling_instance} -> #{@instance_name}: \n---\n#{prompt}\n---")
122
+ end
123
+
124
+ def log_response(response)
125
+ @logger.info(
126
+ "($#{response["total_cost"]} - #{response["duration_ms"]}ms) #{@instance_name} -> #{@calling_instance}: \n---\n#{response["result"]}\n---"
127
+ )
128
+ end
129
+
130
+ def log_streaming_event(event)
131
+ return log_system_message(event) if event["type"] == "system"
132
+
133
+ # Add specific details based on event type
134
+ case event["type"]
135
+ when "assistant"
136
+ log_assistant_message(event["message"])
137
+ when "user"
138
+ log_user_message(event["message"]["content"])
139
+ when "result"
140
+ log_response(event)
141
+ end
142
+ end
143
+
144
+ def log_system_message(event)
145
+ @logger.debug("SYSTEM: #{JSON.pretty_generate(event)}")
146
+ end
147
+
148
+ def log_assistant_message(msg)
149
+ return if msg["stop_reason"] == "end_turn" # that means it is not a thought but the final answer
150
+
151
+ content = msg["content"]
152
+ @logger.debug("ASSISTANT: #{JSON.pretty_generate(content)}")
153
+ tool_calls = content.select { |c| c["type"] == "tool_use" }
154
+ tool_calls.each do |tool_call|
155
+ arguments = tool_call["input"].to_json
156
+ arguments = "#{arguments[0..300]} ...}" if arguments.length > 300
157
+
158
+ @logger.info(
159
+ "Tool call from #{@instance_name} -> Tool: #{tool_call["name"]}, ID: #{tool_call["id"]}, Arguments: #{arguments}"
160
+ )
161
+ end
162
+
163
+ text = content.select { |c| c["type"] == "text" }
164
+ text.each do |t|
165
+ @logger.info("#{@instance_name} is thinking:\n---\n#{t["text"]}\n---")
166
+ end
167
+ end
168
+
169
+ def log_user_message(content)
170
+ @logger.debug("USER: #{JSON.pretty_generate(content)}")
171
+ end
172
+
55
173
  def build_command_array(prompt, options)
56
174
  cmd_array = ["claude"]
57
175
 
58
176
  # Add model if specified
59
177
  cmd_array += ["--model", @model]
60
178
 
179
+ cmd_array << "--verbose"
180
+
61
181
  # Add MCP config if specified
62
182
  cmd_array += ["--mcp-config", @mcp_config] if @mcp_config
63
183
 
@@ -65,7 +185,7 @@ module ClaudeSwarm
65
185
  cmd_array += ["--resume", @session_id] if @session_id && !options[:new_session]
66
186
 
67
187
  # Always use JSON output format for structured responses
68
- cmd_array += ["--output-format", "json"]
188
+ cmd_array += ["--output-format", "stream-json"]
69
189
 
70
190
  # Add non-interactive mode with prompt
71
191
  cmd_array += ["--print", "-p", prompt]
@@ -2,15 +2,13 @@
2
2
 
3
3
  require "fast_mcp"
4
4
  require "json"
5
- require "fileutils"
6
- require "logger"
7
5
  require_relative "claude_code_executor"
6
+ require_relative "task_tool"
7
+ require_relative "session_info_tool"
8
+ require_relative "reset_session_tool"
8
9
 
9
10
  module ClaudeSwarm
10
11
  class ClaudeMcpServer
11
- SWARM_DIR = ".claude-swarm"
12
- SESSIONS_DIR = "sessions"
13
-
14
12
  # Class variables to share state with tool classes
15
13
  class << self
16
14
  attr_accessor :executor, :instance_config, :logger, :session_timestamp, :calling_instance
@@ -23,46 +21,19 @@ module ClaudeSwarm
23
21
  working_directory: instance_config[:directory],
24
22
  model: instance_config[:model],
25
23
  mcp_config: instance_config[:mcp_config_path],
26
- vibe: instance_config[:vibe]
24
+ vibe: instance_config[:vibe],
25
+ instance_name: instance_config[:name],
26
+ calling_instance: calling_instance
27
27
  )
28
28
 
29
- # Setup logging
30
- setup_logging
31
-
32
29
  # Set class variables so tools can access them
33
30
  self.class.executor = @executor
34
31
  self.class.instance_config = @instance_config
35
- self.class.logger = @logger
32
+ self.class.logger = @executor.logger
33
+ self.class.session_timestamp = @executor.session_timestamp
36
34
  self.class.calling_instance = @calling_instance
37
35
  end
38
36
 
39
- private
40
-
41
- def setup_logging
42
- # Use environment variable for session timestamp if available (set by orchestrator)
43
- # Otherwise create a new timestamp
44
- self.class.session_timestamp ||= ENV["CLAUDE_SWARM_SESSION_TIMESTAMP"] || Time.now.strftime("%Y%m%d_%H%M%S")
45
-
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)
49
-
50
- # Create logger with session.log filename
51
- log_filename = "session.log"
52
- log_path = File.join(session_dir, log_filename)
53
- @logger = Logger.new(log_path)
54
- @logger.level = Logger::INFO
55
-
56
- # Custom formatter for better readability
57
- @logger.formatter = proc do |severity, datetime, _progname, msg|
58
- "[#{datetime.strftime("%Y-%m-%d %H:%M:%S.%L")}] [#{severity}] #{msg}\n"
59
- end
60
-
61
- @logger.info("Started MCP server for instance: #{@instance_config[:name]}")
62
- end
63
-
64
- public
65
-
66
37
  def start
67
38
  server = FastMcp::Server.new(
68
39
  name: @instance_config[:name],
@@ -84,120 +55,5 @@ module ClaudeSwarm
84
55
  # Start the stdio server
85
56
  server.start
86
57
  end
87
-
88
- class TaskTool < FastMcp::Tool
89
- tool_name "task"
90
- description "Execute a task using Claude Code"
91
-
92
- arguments do
93
- required(:prompt).filled(:string).description("The task or question for the agent")
94
- optional(:new_session).filled(:bool).description("Start a new session (default: false)")
95
- optional(:system_prompt).filled(:string).description("Override the system prompt for this request")
96
- end
97
-
98
- def call(prompt:, new_session: false, system_prompt: nil)
99
- executor = ClaudeMcpServer.executor
100
- instance_config = ClaudeMcpServer.instance_config
101
- logger = ClaudeMcpServer.logger
102
-
103
- options = {
104
- new_session: new_session,
105
- system_prompt: system_prompt || instance_config[:prompt]
106
- }
107
-
108
- # Add allowed tools from instance config
109
- options[:allowed_tools] = instance_config[:tools] if instance_config[:tools]&.any?
110
-
111
- begin
112
- # Log the request
113
- log_entry = {
114
- timestamp: Time.now.utc.iso8601,
115
- from_instance: ClaudeMcpServer.calling_instance, # The instance making the request
116
- to_instance: instance_config[:name], # This instance is receiving the request
117
- model: instance_config[:model],
118
- working_directory: instance_config[:directory],
119
- session_id: executor.session_id,
120
- request: {
121
- prompt: prompt,
122
- new_session: new_session,
123
- system_prompt: options[:system_prompt],
124
- allowed_tools: options[:allowed_tools]
125
- }
126
- }
127
-
128
- logger.info("REQUEST: #{JSON.pretty_generate(log_entry)}")
129
-
130
- response = executor.execute(prompt, options)
131
-
132
- # Log the response
133
- response_entry = {
134
- timestamp: Time.now.utc.iso8601,
135
- from_instance: instance_config[:name], # This instance is sending the response
136
- to_instance: ClaudeMcpServer.calling_instance, # The instance that made the request receives the response
137
- session_id: executor.session_id, # Update with new session ID if changed
138
- response: {
139
- result: response["result"],
140
- cost_usd: response["cost_usd"],
141
- duration_ms: response["duration_ms"],
142
- is_error: response["is_error"],
143
- total_cost: response["total_cost"]
144
- }
145
- }
146
-
147
- logger.info("RESPONSE: #{JSON.pretty_generate(response_entry)}")
148
-
149
- # Return just the result text as expected by MCP
150
- response["result"]
151
- rescue ClaudeCodeExecutor::ExecutionError => e
152
- logger.error("Execution error for #{instance_config[:name]}: #{e.message}")
153
- raise StandardError, "Execution failed: #{e.message}"
154
- rescue ClaudeCodeExecutor::ParseError => e
155
- logger.error("Parse error for #{instance_config[:name]}: #{e.message}")
156
- raise StandardError, "Parse error: #{e.message}"
157
- rescue StandardError => e
158
- logger.error("Unexpected error for #{instance_config[:name]}: #{e.class} - #{e.message}")
159
- logger.error("Backtrace: #{e.backtrace.join("\n")}")
160
- raise StandardError, "Unexpected error: #{e.message}"
161
- end
162
- end
163
- end
164
-
165
- class SessionInfoTool < FastMcp::Tool
166
- tool_name "session_info"
167
- description "Get information about the current Claude session for this agent"
168
-
169
- arguments do
170
- # No arguments needed
171
- end
172
-
173
- def call
174
- executor = ClaudeMcpServer.executor
175
-
176
- {
177
- has_session: executor.has_session?,
178
- session_id: executor.session_id,
179
- working_directory: executor.working_directory
180
- }
181
- end
182
- end
183
-
184
- class ResetSessionTool < FastMcp::Tool
185
- tool_name "reset_session"
186
- description "Reset the Claude session for this agent, starting fresh on the next task"
187
-
188
- arguments do
189
- # No arguments needed
190
- end
191
-
192
- def call
193
- executor = ClaudeMcpServer.executor
194
- executor.reset_session
195
-
196
- {
197
- success: true,
198
- message: "Session has been reset"
199
- }
200
- end
201
- end
202
58
  end
203
59
  end
@@ -51,32 +51,43 @@ module ClaudeSwarm
51
51
  end
52
52
 
53
53
  # Execute the main instance - this will cascade to other instances via MCP
54
- exec(command)
54
+ Dir.chdir(main_instance[:directory]) do
55
+ system(*command)
56
+ end
55
57
  end
56
58
 
57
59
  private
58
60
 
59
61
  def build_main_command(instance)
60
- parts = []
61
- parts << "cd #{Shellwords.escape(instance[:directory])} &&"
62
- parts << "claude"
63
- parts << "--model #{instance[:model]}"
62
+ parts = [
63
+ "claude",
64
+ "--model",
65
+ instance[:model]
66
+ ]
64
67
 
65
68
  if @vibe
66
69
  parts << "--dangerously-skip-permissions"
67
70
  elsif instance[:tools].any?
68
71
  tools_str = instance[:tools].join(",")
69
- parts << "--allowedTools '#{tools_str}'"
72
+ parts << "--allowedTools"
73
+ parts << tools_str
70
74
  end
71
75
 
72
- parts << "--append-system-prompt #{Shellwords.escape(instance[:prompt])}" if instance[:prompt]
76
+ if instance[:prompt]
77
+ parts << "--append-system-prompt"
78
+ parts << instance[:prompt]
79
+ end
73
80
 
74
81
  mcp_config_path = @generator.mcp_config_path(@config.main_instance)
75
- parts << "--mcp-config #{mcp_config_path}"
82
+ parts << "--mcp-config"
83
+ parts << mcp_config_path
76
84
 
77
- parts << "-p #{Shellwords.escape(@prompt)}" if @prompt
78
-
79
- parts.join(" ")
85
+ if @prompt
86
+ parts << "-p"
87
+ parts << @prompt
88
+ else
89
+ parts << "#{instance[:prompt]}\n\nNow just say 'I am ready to start'"
90
+ end
80
91
  end
81
92
  end
82
93
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeSwarm
4
+ class ResetSessionTool < FastMcp::Tool
5
+ tool_name "reset_session"
6
+ description "Reset the Claude session for this agent, starting fresh on the next task"
7
+
8
+ arguments do
9
+ # No arguments needed
10
+ end
11
+
12
+ def call
13
+ executor = ClaudeMcpServer.executor
14
+ executor.reset_session
15
+
16
+ {
17
+ success: true,
18
+ message: "Session has been reset"
19
+ }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeSwarm
4
+ class SessionInfoTool < FastMcp::Tool
5
+ tool_name "session_info"
6
+ description "Get information about the current Claude session for this agent"
7
+
8
+ arguments do
9
+ # No arguments needed
10
+ end
11
+
12
+ def call
13
+ executor = ClaudeMcpServer.executor
14
+
15
+ {
16
+ has_session: executor.has_session?,
17
+ session_id: executor.session_id,
18
+ working_directory: executor.working_directory
19
+ }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeSwarm
4
+ class TaskTool < FastMcp::Tool
5
+ tool_name "task"
6
+ description "Execute a task using Claude Code"
7
+
8
+ arguments do
9
+ required(:prompt).filled(:string).description("The task or question for the agent")
10
+ optional(:new_session).filled(:bool).description("Start a new session (default: false)")
11
+ optional(:system_prompt).filled(:string).description("Override the system prompt for this request")
12
+ end
13
+
14
+ def call(prompt:, new_session: false, system_prompt: nil)
15
+ executor = ClaudeMcpServer.executor
16
+ instance_config = ClaudeMcpServer.instance_config
17
+
18
+ options = {
19
+ new_session: new_session,
20
+ system_prompt: system_prompt || instance_config[:prompt]
21
+ }
22
+
23
+ # Add allowed tools from instance config
24
+ options[:allowed_tools] = instance_config[:tools] if instance_config[:tools]&.any?
25
+
26
+ response = executor.execute(prompt, options)
27
+
28
+ # Return just the result text as expected by MCP
29
+ response["result"]
30
+ end
31
+ end
32
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeSwarm
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.6"
5
5
  end
data/sdk-docs.md ADDED
@@ -0,0 +1,426 @@
1
+ # SDK
2
+
3
+ > Programmatically integrate Claude Code into your applications using the SDK.
4
+
5
+ The Claude Code SDK allows developers to programmatically integrate Claude Code into their applications. It enables running Claude Code as a subprocess, providing a way to build AI-powered coding assistants and tools that leverage Claude's capabilities.
6
+
7
+ The SDK currently support command line usage. TypeScript and Python SDKs are coming soon.
8
+
9
+ ## Authentication
10
+
11
+ To use the Claude Code SDK, we recommend creating a dedicated API key:
12
+
13
+ 1. Create an Anthropic API key in the [Anthropic Console](https://console.anthropic.com/)
14
+ 2. Then, set the `ANTHROPIC_API_KEY` environment variable. We recommend storing this key securely (eg. using a Github [secret](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions))
15
+
16
+ ## Basic SDK usage
17
+
18
+ The Claude Code SDK allows you to use Claude Code in non-interactive mode from your applications. Here's a basic example:
19
+
20
+ ```bash
21
+ # Run a single prompt and exit (print mode)
22
+ $ claude -p "Write a function to calculate Fibonacci numbers"
23
+
24
+ # Using a pipe to provide stdin
25
+ $ echo "Explain this code" | claude -p
26
+
27
+ # Output in JSON format with metadata
28
+ $ claude -p "Generate a hello world function" --output-format json
29
+
30
+ # Stream JSON output as it arrives
31
+ $ claude -p "Build a React component" --output-format stream-json
32
+ ```
33
+
34
+ ## Advanced usage
35
+
36
+ ### Multi-turn conversations
37
+
38
+ For multi-turn conversations, you can resume conversations or continue from the most recent session:
39
+
40
+ ```bash
41
+ # Continue the most recent conversation
42
+ $ claude --continue
43
+
44
+ # Continue and provide a new prompt
45
+ $ claude --continue "Now refactor this for better performance"
46
+
47
+ # Resume a specific conversation by session ID
48
+ $ claude --resume 550e8400-e29b-41d4-a716-446655440000
49
+
50
+ # Resume in print mode (non-interactive)
51
+ $ claude -p --resume 550e8400-e29b-41d4-a716-446655440000 "Update the tests"
52
+
53
+ # Continue in print mode (non-interactive)
54
+ $ claude -p --continue "Add error handling"
55
+ ```
56
+
57
+ ### Custom system prompts
58
+
59
+ You can provide custom system prompts to guide Claude's behavior:
60
+
61
+ ```bash
62
+ # Override system prompt (only works with --print)
63
+ $ claude -p "Build a REST API" --system-prompt "You are a senior backend engineer. Focus on security, performance, and maintainability."
64
+
65
+ # System prompt with specific requirements
66
+ $ claude -p "Create a database schema" --system-prompt "You are a database architect. Use PostgreSQL best practices and include proper indexing."
67
+ ```
68
+
69
+ You can also append instructions to the default system prompt:
70
+
71
+ ```bash
72
+ # Append system prompt (only works with --print)
73
+ $ claude -p "Build a REST API" --append-system-prompt "After writing code, be sure to code review yourself."
74
+ ```
75
+
76
+ ### MCP Configuration
77
+
78
+ The Model Context Protocol (MCP) allows you to extend Claude Code with additional tools and resources from external servers. Using the `--mcp-config` flag, you can load MCP servers that provide specialized capabilities like database access, API integrations, or custom tooling.
79
+
80
+ Create a JSON configuration file with your MCP servers:
81
+
82
+ ```json
83
+ {
84
+ "mcpServers": {
85
+ "filesystem": {
86
+ "command": "npx",
87
+ "args": [
88
+ "-y",
89
+ "@modelcontextprotocol/server-filesystem",
90
+ "/path/to/allowed/files"
91
+ ]
92
+ },
93
+ "github": {
94
+ "command": "npx",
95
+ "args": ["-y", "@modelcontextprotocol/server-github"],
96
+ "env": {
97
+ "GITHUB_TOKEN": "your-github-token"
98
+ }
99
+ }
100
+ }
101
+ }
102
+ ```
103
+
104
+ Then use it with Claude Code:
105
+
106
+ ```bash
107
+ # Load MCP servers from configuration
108
+ $ claude -p "List all files in the project" --mcp-config mcp-servers.json
109
+
110
+ # Important: MCP tools must be explicitly allowed using --allowedTools
111
+ # MCP tools follow the format: mcp__$serverName__$toolName
112
+ $ claude -p "Search for TODO comments" \
113
+ --mcp-config mcp-servers.json \
114
+ --allowedTools "mcp__filesystem__read_file,mcp__filesystem__list_directory"
115
+
116
+ # Use an MCP tool for handling permission prompts in non-interactive mode
117
+ $ claude -p "Deploy the application" \
118
+ --mcp-config mcp-servers.json \
119
+ --allowedTools "mcp__permissions__approve" \
120
+ --permission-prompt-tool mcp__permissions__approve
121
+ ```
122
+
123
+ Note: When using MCP tools, you must explicitly allow them using the `--allowedTools` flag. MCP tool names follow the pattern `mcp__<serverName>__<toolName>` where:
124
+
125
+ * `serverName` is the key from your MCP configuration file
126
+ * `toolName` is the specific tool provided by that server
127
+
128
+ This security measure ensures that MCP tools are only used when explicitly permitted.
129
+
130
+ ### Custom permission prompt tool
131
+
132
+ Optionally, use `--permission-prompt-tool` to pass in an MCP tool that we will use to check whether or not the user grants the model permissions to invoke a given tool. When the model invokes a tool the following happens:
133
+
134
+ 1. We first check permission settings: all [settings.json files](/en/docs/claude-code/settings), as well as `--allowedTools` and `--disallowedTools` passed into the SDK; if one of these allows or denies the tool call, we proceed with the tool call
135
+ 2. Otherwise, we invoke the MCP tool you provided in `--permission-prompt-tool`
136
+
137
+ The `--permission-prompt-tool` MCP tool is passed the tool name and input, and must return a JSON-stringified payload with the result. The payload must be one of:
138
+
139
+ ```ts
140
+ // tool call is allowed
141
+ {
142
+ "behavior": "allow",
143
+ "updatedInput": {...}, // updated input, or just return back the original input
144
+ }
145
+
146
+ // tool call is denied
147
+ {
148
+ "behavior": "deny",
149
+ "message": "..." // human-readable string explaining why the permission was denied
150
+ }
151
+ ```
152
+
153
+ For example, a TypeScript MCP permission prompt tool implementation might look like this:
154
+
155
+ ```ts
156
+ const server = new McpServer({
157
+ name: "Test permission prompt MCP Server",
158
+ version: "0.0.1",
159
+ });
160
+
161
+ server.tool(
162
+ "approval_prompt",
163
+ 'Simulate a permission check - approve if the input contains "allow", otherwise deny',
164
+ {
165
+ tool_name: z.string().describe("The tool requesting permission"),
166
+ input: z.object({}).passthrough().describe("The input for the tool"),
167
+ },
168
+ async ({ tool_name, input }) => {
169
+ return {
170
+ content: [
171
+ {
172
+ type: "text",
173
+ text: JSON.stringify(
174
+ JSON.stringify(input).includes("allow")
175
+ ? {
176
+ behavior: "allow",
177
+ updatedInput: input,
178
+ }
179
+ : {
180
+ behavior: "deny",
181
+ message: "Permission denied by test approval_prompt tool",
182
+ }
183
+ ),
184
+ },
185
+ ],
186
+ };
187
+ }
188
+ );
189
+ ```
190
+
191
+ To use this tool, add your MCP server (eg. with `--mcp-config`), then invoke the SDK like so:
192
+
193
+ ```sh
194
+ claude -p "..." \
195
+ --permission-prompt-tool mcp__test-server__approval_prompt \
196
+ --mcp-config my-config.json
197
+ ```
198
+
199
+ Usage notes:
200
+
201
+ * Use `updatedInput` to tell the model that the permission prompt mutated its input; otherwise, set `updatedInput` to the original input, as in the example above. For example, if the tool shows a file edit diff to the user and lets them edit the diff manually, the permission prompt tool should return that updated edit.
202
+ * The payload must be JSON-stringified
203
+
204
+ ## Available CLI options
205
+
206
+ The SDK leverages all the CLI options available in Claude Code. Here are the key ones for SDK usage:
207
+
208
+ | Flag | Description | Example |
209
+ | :------------------------- | :--------------------------------------------------------------- | :------------------------------------------------------------- |
210
+ | `--print`, `-p` | Run in non-interactive mode | `claude -p "query"` |
211
+ | `--output-format` | Specify output format (`text`, `json`, `stream-json`) | `claude -p --output-format json` |
212
+ | `--resume`, `-r` | Resume a conversation by session ID | `claude --resume abc123` |
213
+ | `--continue`, `-c` | Continue the most recent conversation | `claude --continue` |
214
+ | `--verbose` | Enable verbose logging | `claude --verbose` |
215
+ | `--max-turns` | Limit agentic turns in non-interactive mode | `claude --max-turns 3` |
216
+ | `--system-prompt` | Override system prompt (only with `--print`) | `claude --system-prompt "Custom instruction"` |
217
+ | `--append-system-prompt` | Append to system prompt (only with `--print`) | `claude --append-system-prompt "Custom instruction"` |
218
+ | `--allowedTools` | Comma/space-separated list of allowed tools (includes MCP tools) | `claude --allowedTools "Bash(npm install),mcp__filesystem__*"` |
219
+ | `--disallowedTools` | Comma/space-separated list of denied tools | `claude --disallowedTools "Bash(git commit),mcp__github__*"` |
220
+ | `--mcp-config` | Load MCP servers from a JSON file | `claude --mcp-config servers.json` |
221
+ | `--permission-prompt-tool` | MCP tool for handling permission prompts (only with `--print`) | `claude --permission-prompt-tool mcp__auth__prompt` |
222
+
223
+ For a complete list of CLI options and features, see the [CLI usage](/en/docs/claude-code/cli-usage) documentation.
224
+
225
+ ## Output formats
226
+
227
+ The SDK supports multiple output formats:
228
+
229
+ ### Text output (default)
230
+
231
+ Returns just the response text:
232
+
233
+ ```bash
234
+ $ claude -p "Explain file src/components/Header.tsx"
235
+ # Output: This is a React component showing...
236
+ ```
237
+
238
+ ### JSON output
239
+
240
+ Returns structured data including metadata:
241
+
242
+ ```bash
243
+ $ claude -p "How does the data layer work?" --output-format json
244
+ ```
245
+
246
+ Response format:
247
+
248
+ ```json
249
+ {
250
+ "type": "result",
251
+ "subtype": "success",
252
+ "cost_usd": 0.003,
253
+ "is_error": false,
254
+ "duration_ms": 1234,
255
+ "duration_api_ms": 800,
256
+ "num_turns": 6,
257
+ "result": "The response text here...",
258
+ "session_id": "abc123"
259
+ }
260
+ ```
261
+
262
+ ### Streaming JSON output
263
+
264
+ Streams each message as it is received:
265
+
266
+ ```bash
267
+ $ claude -p "Build an application" --output-format stream-json
268
+ ```
269
+
270
+ Each conversation begins with an initial `init` system message, followed by a list of user and assistant messages, followed by a final `result` system message with stats. Each message is emitted as a separate JSON object.
271
+
272
+ ## Message schema
273
+
274
+ Messages returned from the JSON API are strictly typed according to the following schema:
275
+
276
+ ```ts
277
+ type SDKMessage =
278
+ // An assistant message
279
+ | {
280
+ type: "assistant";
281
+ message: Message; // from Anthropic SDK
282
+ session_id: string;
283
+ }
284
+
285
+ // A user message
286
+ | {
287
+ type: "user";
288
+ message: MessageParam; // from Anthropic SDK
289
+ session_id: string;
290
+ }
291
+
292
+ // Emitted as the last message
293
+ | {
294
+ type: "result";
295
+ subtype: "success";
296
+ cost_usd: float;
297
+ duration_ms: float;
298
+ duration_api_ms: float;
299
+ is_error: boolean;
300
+ num_turns: int;
301
+ result: string;
302
+ session_id: string;
303
+ }
304
+
305
+ // Emitted as the last message, when we've reached the maximum number of turns
306
+ | {
307
+ type: "result";
308
+ subtype: "error_max_turns";
309
+ cost_usd: float;
310
+ duration_ms: float;
311
+ duration_api_ms: float;
312
+ is_error: boolean;
313
+ num_turns: int;
314
+ session_id: string;
315
+ }
316
+
317
+ // Emitted as the first message at the start of a conversation
318
+ | {
319
+ type: "system";
320
+ subtype: "init";
321
+ session_id: string;
322
+ tools: string[];
323
+ mcp_servers: {
324
+ name: string;
325
+ status: string;
326
+ }[];
327
+ };
328
+ ```
329
+
330
+ We will soon publish these types in a JSONSchema-compatible format. We use semantic versioning for the main Claude Code package to communicate breaking changes to this format.
331
+
332
+ `Message` and `MessageParam` types are available in Anthropic SDKs. For example, see the Anthropic [TypeScript](https://github.com/anthropics/anthropic-sdk-typescript) and [Python](https://github.com/anthropics/anthropic-sdk-python/) SDKs.
333
+
334
+ ## Examples
335
+
336
+ ### Simple script integration
337
+
338
+ ```bash
339
+ #!/bin/bash
340
+
341
+ # Simple function to run Claude and check exit code
342
+ run_claude() {
343
+ local prompt="$1"
344
+ local output_format="${2:-text}"
345
+
346
+ if claude -p "$prompt" --output-format "$output_format"; then
347
+ echo "Success!"
348
+ else
349
+ echo "Error: Claude failed with exit code $?" >&2
350
+ return 1
351
+ fi
352
+ }
353
+
354
+ # Usage examples
355
+ run_claude "Write a Python function to read CSV files"
356
+ run_claude "Optimize this database query" "json"
357
+ ```
358
+
359
+ ### Processing files with Claude
360
+
361
+ ```bash
362
+ # Process a file through Claude
363
+ $ cat mycode.py | claude -p "Review this code for bugs"
364
+
365
+ # Process multiple files
366
+ $ for file in *.js; do
367
+ echo "Processing $file..."
368
+ claude -p "Add JSDoc comments to this file:" < "$file" > "${file}.documented"
369
+ done
370
+
371
+ # Use Claude in a pipeline
372
+ $ grep -l "TODO" *.py | while read file; do
373
+ claude -p "Fix all TODO items in this file" < "$file"
374
+ done
375
+ ```
376
+
377
+ ### Session management
378
+
379
+ ```bash
380
+ # Start a session and capture the session ID
381
+ $ claude -p "Initialize a new project" --output-format json | jq -r '.session_id' > session.txt
382
+
383
+ # Continue with the same session
384
+ $ claude -p --resume "$(cat session.txt)" "Add unit tests"
385
+ ```
386
+
387
+ ## Best practices
388
+
389
+ 1. **Use JSON output format** for programmatic parsing of responses:
390
+
391
+ ```bash
392
+ # Parse JSON response with jq
393
+ result=$(claude -p "Generate code" --output-format json)
394
+ code=$(echo "$result" | jq -r '.result')
395
+ cost=$(echo "$result" | jq -r '.cost_usd')
396
+ ```
397
+
398
+ 2. **Handle errors gracefully** - check exit codes and stderr:
399
+
400
+ ```bash
401
+ if ! claude -p "$prompt" 2>error.log; then
402
+ echo "Error occurred:" >&2
403
+ cat error.log >&2
404
+ exit 1
405
+ fi
406
+ ```
407
+
408
+ 3. **Use session management** for maintaining context in multi-turn conversations
409
+
410
+ 4. **Consider timeouts** for long-running operations:
411
+
412
+ ```bash
413
+ timeout 300 claude -p "$complex_prompt" || echo "Timed out after 5 minutes"
414
+ ```
415
+
416
+ 5. **Respect rate limits** when making multiple requests by adding delays between calls
417
+
418
+ ## Real-world applications
419
+
420
+ The Claude Code SDK enables powerful integrations with your development workflow. One notable example is the [Claude Code GitHub Actions](/en/docs/claude-code/github-actions), which uses the SDK to provide automated code review, PR creation, and issue triage capabilities directly in your GitHub workflow.
421
+
422
+ ## Related resources
423
+
424
+ * [CLI usage and controls](/en/docs/claude-code/cli-usage) - Complete CLI documentation
425
+ * [GitHub Actions integration](/en/docs/claude-code/github-actions) - Automate your GitHub workflow with Claude
426
+ * [Tutorials](/en/docs/claude-code/tutorials) - Step-by-step guides for common use cases
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.4
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Arruda
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-05-30 00:00:00.000000000 Z
10
+ date: 2025-06-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: thor
@@ -67,7 +67,11 @@ files:
67
67
  - lib/claude_swarm/configuration.rb
68
68
  - lib/claude_swarm/mcp_generator.rb
69
69
  - lib/claude_swarm/orchestrator.rb
70
+ - lib/claude_swarm/reset_session_tool.rb
71
+ - lib/claude_swarm/session_info_tool.rb
72
+ - lib/claude_swarm/task_tool.rb
70
73
  - lib/claude_swarm/version.rb
74
+ - sdk-docs.md
71
75
  homepage: https://github.com/parruda/claude-swarm
72
76
  licenses: []
73
77
  metadata: