claude_swarm 0.1.7 → 0.1.8

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: 0b5aa9e698fe0c055fc16aa5263df48a2c6e473179d5472e06f83c8845a3447c
4
- data.tar.gz: ce36d61d30177b38e2f9dbb6f9b00fc1222f0d5ee4cb4539e75f912ad9777904
3
+ metadata.gz: 4694bdf654adfb2784e4156d504ace610c9b48bd7aedafb4d5571ee0483325e6
4
+ data.tar.gz: 9f26361aac4c2815b11f95ab28a67fbb684377d691b7ee5a1b99c89a1a4fa3c1
5
5
  SHA512:
6
- metadata.gz: ffbac2fb59e4f76ab39efe172a89d1a11b0893efd763770ba3f4c6e52785df44d19cc0db9a8dd87e2df52c05c7d8cceadc47deb2a8304e34ccb2c921b781842c
7
- data.tar.gz: dd4eeb38f8d56c3cd41570fe0be0ccda029240893780d95c82c134ad02d91c840ba8fd3fb344dc205c7a3345e37fbce33b8f633abd076390783b31babaf6014d
6
+ metadata.gz: 8c5d95adfea48bb560e8ed04d1149284fa199e72a9051e85f7e3e23222a1c117c96b6eb977a418bc573d41a6e6c846d7071dd552121d3810a33b0937ac3228c4
7
+ data.tar.gz: aaf42838c5caf597bde330171ebd0aca702089731794f2c45df395b177fe6871c96f27f5838da587a9454de7cc397ca8fae7f910c6f61b1f775f6c2911444823
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## [0.1.8]
2
+
3
+ ### Added
4
+ - **Disallowed tools support**: New `disallowed_tools` YAML key for explicitly denying specific tools (takes precedence over allowed tools)
5
+
6
+ ### Changed
7
+ - **Renamed YAML key**: `tools` renamed to `allowed_tools` while maintaining backward compatibility
8
+ - Tool permissions now support both allow and deny patterns, with deny taking precedence
9
+ - Both `--allowedTools` and `--disallowedTools` are passed as comma-separated lists to Claude
10
+ - New CLI option `--stream-logs` - can only be used with `-p`
11
+
1
12
  ## [0.1.7]
2
13
 
3
14
  ### Added
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Claude Swarm
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/claude_swarm.svg)](https://badge.fury.io/rb/claude_swarm)
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
+
3
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.
4
7
 
5
8
  ## Installation
@@ -24,7 +27,7 @@ bundle install
24
27
 
25
28
  ## Prerequisites
26
29
 
27
- - Ruby 3.1.0 or higher
30
+ - Ruby 3.2.0 or higher
28
31
  - Claude CLI installed and configured
29
32
  - Any MCP servers you plan to use (optional)
30
33
 
@@ -45,7 +48,7 @@ swarm:
45
48
  directory: .
46
49
  model: opus
47
50
  connections: [frontend, backend]
48
- tools: # Tools aren't required if you run it with `--vibe`
51
+ allowed_tools: # Tools aren't required if you run it with `--vibe`
49
52
  - Read
50
53
  - Edit
51
54
  - Bash
@@ -53,7 +56,7 @@ swarm:
53
56
  description: "Frontend specialist handling UI and user experience"
54
57
  directory: ./frontend
55
58
  model: opus
56
- tools:
59
+ allowed_tools:
57
60
  - Edit
58
61
  - Write
59
62
  - Bash
@@ -61,7 +64,7 @@ swarm:
61
64
  description: "Backend developer managing APIs and data layer"
62
65
  directory: ./backend
63
66
  model: opus
64
- tools:
67
+ allowed_tools:
65
68
  - Edit
66
69
  - Write
67
70
  - Bash
@@ -98,7 +101,7 @@ swarm:
98
101
  model: opus
99
102
  connections: [frontend_lead, backend_lead, mobile_lead, devops]
100
103
  prompt: "You are the system architect coordinating between different service teams"
101
- tools: [Read, Edit, WebSearch]
104
+ allowed_tools: [Read, Edit, WebSearch]
102
105
 
103
106
  frontend_lead:
104
107
  description: "Frontend team lead overseeing React development"
@@ -106,21 +109,21 @@ swarm:
106
109
  model: opus
107
110
  connections: [react_dev, css_expert]
108
111
  prompt: "You lead the web frontend team working with React"
109
- tools: [Read, Edit, Bash]
112
+ allowed_tools: [Read, Edit, Bash]
110
113
 
111
114
  react_dev:
112
115
  description: "React developer specializing in components and state management"
113
116
  directory: ./web-frontend/src
114
117
  model: opus
115
118
  prompt: "You specialize in React components and state management"
116
- tools: [Edit, Write, "Bash(npm:*)"]
119
+ allowed_tools: [Edit, Write, "Bash(npm:*)"]
117
120
 
118
121
  css_expert:
119
122
  description: "CSS specialist handling styling and responsive design"
120
123
  directory: ./web-frontend/styles
121
124
  model: opus
122
125
  prompt: "You handle all CSS and styling concerns"
123
- tools: [Edit, Write, Read]
126
+ allowed_tools: [Edit, Write, Read]
124
127
 
125
128
  backend_lead:
126
129
  description: "Backend team lead managing API development"
@@ -128,21 +131,21 @@ swarm:
128
131
  model: opus
129
132
  connections: [api_dev, database_expert]
130
133
  prompt: "You lead the API backend team"
131
- tools: [Read, Edit, Bash]
134
+ allowed_tools: [Read, Edit, Bash]
132
135
 
133
136
  api_dev:
134
137
  description: "API developer building REST endpoints"
135
138
  directory: ./api-server/src
136
139
  model: opus
137
140
  prompt: "You develop REST API endpoints"
138
- tools: [Edit, Write, Bash]
141
+ allowed_tools: [Edit, Write, Bash]
139
142
 
140
143
  database_expert:
141
144
  description: "Database specialist managing schemas and migrations"
142
145
  directory: ./api-server/db
143
146
  model: opus
144
147
  prompt: "You handle database schema and migrations"
145
- tools: [Edit, Write, "Bash(psql:*, migrate:*)"]
148
+ allowed_tools: [Edit, Write, "Bash(psql:*, migrate:*)"]
146
149
 
147
150
  mobile_lead:
148
151
  description: "Mobile team lead coordinating cross-platform development"
@@ -150,28 +153,28 @@ swarm:
150
153
  model: opus
151
154
  connections: [ios_dev, android_dev]
152
155
  prompt: "You coordinate mobile development across platforms"
153
- tools: [Read, Edit]
156
+ allowed_tools: [Read, Edit]
154
157
 
155
158
  ios_dev:
156
159
  description: "iOS developer building native Apple applications"
157
160
  directory: ./mobile-app/ios
158
161
  model: opus
159
162
  prompt: "You develop the iOS application"
160
- tools: [Edit, Write, "Bash(xcodebuild:*, pod:*)"]
163
+ allowed_tools: [Edit, Write, "Bash(xcodebuild:*, pod:*)"]
161
164
 
162
165
  android_dev:
163
166
  description: "Android developer creating native Android apps"
164
167
  directory: ./mobile-app/android
165
168
  model: opus
166
169
  prompt: "You develop the Android application"
167
- tools: [Edit, Write, "Bash(gradle:*, adb:*)"]
170
+ allowed_tools: [Edit, Write, "Bash(gradle:*, adb:*)"]
168
171
 
169
172
  devops:
170
173
  description: "DevOps engineer managing CI/CD and infrastructure"
171
174
  directory: ./infrastructure
172
175
  model: opus
173
176
  prompt: "You handle CI/CD and infrastructure"
174
- tools: [Read, Edit, "Bash(docker:*, kubectl:*)"]
177
+ allowed_tools: [Read, Edit, "Bash(docker:*, kubectl:*)"]
175
178
  ```
176
179
 
177
180
  In this setup:
@@ -205,7 +208,8 @@ Each instance can have:
205
208
  - **directory**: Working directory for this instance (can use ~ for home)
206
209
  - **model**: Claude model to use (opus, sonnet, haiku)
207
210
  - **connections**: Array of other instances this one can communicate with
208
- - **tools**: Array of tools this instance can use
211
+ - **allowed_tools**: Array of tools this instance can use (backward compatible with `tools`)
212
+ - **disallowed_tools**: Array of tools to explicitly deny (takes precedence over allowed_tools)
209
213
  - **mcps**: Array of additional MCP servers to connect
210
214
  - **prompt**: Custom system prompt to append to the instance
211
215
  - **vibe**: Enable vibe mode (--dangerously-skip-permissions) for this instance (default: false)
@@ -218,13 +222,16 @@ instance_name:
218
222
  connections: [other_instance1, other_instance2]
219
223
  prompt: "You are a specialized agent focused on..."
220
224
  vibe: false # Set to true to skip all permission checks for this instance
221
- tools:
225
+ allowed_tools:
222
226
  - Read
223
227
  - Edit
224
228
  - Write
225
229
  - Bash
226
230
  - WebFetch
227
231
  - WebSearch
232
+ disallowed_tools: # Optional: explicitly deny specific tools
233
+ - "Write(*.log)"
234
+ - "Bash(rm:*)"
228
235
  mcps:
229
236
  - name: server_name
230
237
  type: stdio
@@ -260,23 +267,27 @@ mcps:
260
267
  Specify which tools each instance can use:
261
268
 
262
269
  ```yaml
263
- tools:
270
+ allowed_tools:
264
271
  - Bash # Command execution
265
272
  - Edit # File editing
266
273
  - Write # File creation
267
274
  - Read # File reading
268
275
  - WebFetch # Fetch web content
269
276
  - WebSearch # Search the web
277
+
278
+ disallowed_tools: # Optional: explicitly deny specific tools
279
+ - "Write(*.md)" # Don't allow writing markdown files
280
+ - "Bash(rm:*)" # Don't allow rm commands
270
281
  ```
271
282
 
272
- Tools are passed to Claude using the `--allowedTools` flag with comma-separated values.
283
+ Tools are passed to Claude using the `--allowedTools` and `--disallowedTools` flags with comma-separated values. Disallowed tools take precedence over allowed tools.
273
284
 
274
285
  #### Tool Restrictions
275
286
 
276
287
  You can restrict tools with pattern-based filters:
277
288
 
278
289
  ```yaml
279
- tools:
290
+ allowed_tools:
280
291
  - Read # Unrestricted read access
281
292
  - Edit # Unrestricted edit access
282
293
  - "Bash(npm:*)" # Only allow npm commands
@@ -299,7 +310,7 @@ swarm:
299
310
  model: opus
300
311
  connections: [frontend, backend, devops]
301
312
  prompt: "You are the lead architect responsible for system design and code quality"
302
- tools:
313
+ allowed_tools:
303
314
  - Read
304
315
  - Edit
305
316
  - WebSearch
@@ -310,7 +321,7 @@ swarm:
310
321
  model: opus
311
322
  connections: [architect]
312
323
  prompt: "You specialize in React, TypeScript, and modern frontend development"
313
- tools:
324
+ allowed_tools:
314
325
  - Edit
315
326
  - Write
316
327
  - Bash
@@ -320,7 +331,7 @@ swarm:
320
331
  directory: ./backend
321
332
  model: opus
322
333
  connections: [architect, database]
323
- tools:
334
+ allowed_tools:
324
335
  - Edit
325
336
  - Write
326
337
  - Bash
@@ -329,7 +340,7 @@ swarm:
329
340
  description: "Database administrator managing data persistence"
330
341
  directory: ./db
331
342
  model: haiku
332
- tools:
343
+ allowed_tools:
333
344
  - Read
334
345
  - Bash
335
346
 
@@ -338,7 +349,7 @@ swarm:
338
349
  directory: .
339
350
  model: opus
340
351
  connections: [architect]
341
- tools:
352
+ allowed_tools:
342
353
  - Read
343
354
  - Edit
344
355
  - Bash
@@ -357,7 +368,7 @@ swarm:
357
368
  directory: ~/research
358
369
  model: opus
359
370
  connections: [data_analyst, writer]
360
- tools:
371
+ allowed_tools:
361
372
  - Read
362
373
  - WebSearch
363
374
  - WebFetch
@@ -370,7 +381,7 @@ swarm:
370
381
  description: "Data analyst processing research data and statistics"
371
382
  directory: ~/research/data
372
383
  model: opus
373
- tools:
384
+ allowed_tools:
374
385
  - Read
375
386
  - Write
376
387
  - Bash
@@ -384,7 +395,7 @@ swarm:
384
395
  description: "Technical writer preparing research documentation"
385
396
  directory: ~/research/papers
386
397
  model: opus
387
- tools:
398
+ allowed_tools:
388
399
  - Edit
389
400
  - Write
390
401
  - Read
@@ -411,14 +422,14 @@ swarm:
411
422
  description: "Worker with restricted permissions"
412
423
  directory: ./sensitive
413
424
  model: sonnet
414
- tools: [Read, "Bash(ls:*)"] # Only allow read and ls commands
425
+ allowed_tools: [Read, "Bash(ls:*)"] # Only allow read and ls commands
415
426
 
416
427
  trusted_worker:
417
428
  description: "Trusted worker with more permissions"
418
429
  directory: ./workspace
419
430
  model: sonnet
420
431
  vibe: true # This instance also skips permissions
421
- tools: [] # Tools list ignored when vibe: true
432
+ allowed_tools: [] # Tools list ignored when vibe: true
422
433
  ```
423
434
 
424
435
  ### Command Line Options
@@ -443,6 +454,7 @@ claude-swarm version
443
454
 
444
455
  # Start permission MCP server (for testing/debugging)
445
456
  claude-swarm tools-mcp --allowed-tools 'mcp__frontend__*,mcp__backend__*'
457
+ claude-swarm tools-mcp --allowed-tools 'Read,Edit' --disallowed-tools 'Edit(*.log)'
446
458
 
447
459
  # Internal command for MCP server (used by connected instances)
448
460
  claude-swarm mcp-serve INSTANCE_NAME --config CONFIG_FILE --session-timestamp TIMESTAMP
@@ -458,6 +470,7 @@ claude-swarm mcp-serve INSTANCE_NAME --config CONFIG_FILE --session-timestamp TI
458
470
  3. **Tool Permissions**: Claude Swarm automatically manages tool permissions:
459
471
  - Each instance's configured tools are allowed via the permission MCP
460
472
  - Supports wildcard patterns (e.g., `mcp__frontend__*` allows all frontend MCP tools)
473
+ - Disallowed tools take precedence over allowed tools for fine-grained control
461
474
  - Eliminates the need to manually accept each tool or use global `--vibe` mode
462
475
  - Per-instance `vibe: true` skips all permission checks for that specific instance
463
476
  - The permission MCP uses `--permission-prompt-tool` to check tool access
@@ -537,6 +550,20 @@ bundle exec rake release # Release gem to RubyGems.org
537
550
  rake # Default: runs both tests and RuboCop
538
551
  ```
539
552
 
553
+ ### Release Process
554
+
555
+ The gem is automatically published to RubyGems when a new release is created on GitHub:
556
+
557
+ 1. Update the version number in `lib/claude_swarm/version.rb`
558
+ 2. Update `CHANGELOG.md` with the new version's changes
559
+ 3. Commit the changes: `git commit -am "Bump version to x.y.z"`
560
+ 4. Create a version tag: `git tag -a vx.y.z -m "Release version x.y.z"`
561
+ 5. Push the changes and tag: `git push && git push --tags`
562
+ 6. The GitHub workflow will create a draft release - review and publish it
563
+ 7. Once published, the gem will be automatically built and pushed to RubyGems
564
+
565
+ **Note**: You need to set up the `RUBYGEMS_AUTH_TOKEN` secret in your GitHub repository settings with your RubyGems API key.
566
+
540
567
  ## Contributing
541
568
 
542
569
  Bug reports and pull requests are welcome on GitHub at https://github.com/parruda/claude-swarm.
data/RELEASING.md ADDED
@@ -0,0 +1,110 @@
1
+ # Releasing Claude Swarm
2
+
3
+ This guide walks through the process of releasing a new version of Claude Swarm.
4
+
5
+ ## Prerequisites
6
+
7
+ - Ensure you have push access to the repository
8
+ - Ensure the `RUBYGEMS_AUTH_TOKEN` secret is configured in GitHub repository settings
9
+ - Ensure all tests are passing on the main branch
10
+
11
+ ## Release Steps
12
+
13
+ ### 1. Update Version
14
+
15
+ Edit `lib/claude_swarm/version.rb` and update the version number:
16
+
17
+ ```ruby
18
+ module ClaudeSwarm
19
+ VERSION = "0.1.8" # Update this
20
+ end
21
+ ```
22
+
23
+ ### 2. Update Changelog
24
+
25
+ Move items from the `[Unreleased]` section to a new version section in `CHANGELOG.md`:
26
+
27
+ ```markdown
28
+ ## [Unreleased]
29
+
30
+ ## [0.1.8] - 2025-06-02
31
+
32
+ ### Added
33
+ - Feature X
34
+
35
+ ### Changed
36
+ - Enhancement Y
37
+
38
+ ### Fixed
39
+ - Bug Z
40
+ ```
41
+
42
+ ### 3. Commit Changes
43
+
44
+ ```bash
45
+ git add lib/claude_swarm/version.rb CHANGELOG.md
46
+ git commit -m "Bump version to 0.1.8"
47
+ ```
48
+
49
+ ### 4. Create and Push Tag
50
+
51
+ ```bash
52
+ git tag -a v0.1.8 -m "Release version 0.1.8"
53
+ git push origin main
54
+ git push origin v0.1.8
55
+ ```
56
+
57
+ ### 5. Review Draft Release
58
+
59
+ The GitHub workflow will automatically create a draft release. Review it at:
60
+ https://github.com/parruda/claude-swarm/releases
61
+
62
+ ### 6. Publish Release
63
+
64
+ Once you're satisfied with the release notes, click "Publish release". This will trigger the workflow to:
65
+ - Run tests
66
+ - Build the gem
67
+ - Publish to RubyGems.org
68
+ - Publish to GitHub Packages
69
+
70
+ ### 7. Verify Publication
71
+
72
+ Check that the gem was published successfully:
73
+ - RubyGems: https://rubygems.org/gems/claude_swarm
74
+ - GitHub Packages: https://github.com/parruda/claude-swarm/packages
75
+
76
+ ## Troubleshooting
77
+
78
+ ### Failed Publication
79
+
80
+ If the release workflow fails:
81
+
82
+ 1. Check the workflow logs at: https://github.com/parruda/claude-swarm/actions
83
+ 2. Common issues:
84
+ - Invalid `RUBYGEMS_AUTH_TOKEN` secret
85
+ - Test failures
86
+ - RuboCop violations
87
+ - Network issues
88
+
89
+ ### Manual Release
90
+
91
+ If you need to release manually:
92
+
93
+ ```bash
94
+ # Ensure you're on the correct tag
95
+ git checkout v0.1.8
96
+
97
+ # Build the gem
98
+ gem build claude_swarm.gemspec
99
+
100
+ # Push to RubyGems (requires credentials)
101
+ gem push claude_swarm-0.1.8.gem
102
+ ```
103
+
104
+ ## Post-Release
105
+
106
+ After a successful release:
107
+
108
+ 1. Create a new `[Unreleased]` section in `CHANGELOG.md`
109
+ 2. Announce the release (optional)
110
+ 3. Update any dependent projects
@@ -196,13 +196,22 @@ module ClaudeSwarm
196
196
  # Add any allowed tools or vibe flag
197
197
  if @vibe
198
198
  cmd_array << "--dangerously-skip-permissions"
199
- elsif options[:allowed_tools]
200
- tools = Array(options[:allowed_tools]).join(",")
201
- cmd_array += ["--allowedTools", tools]
202
- end
199
+ else
200
+ # Add allowed tools if any
201
+ if options[:allowed_tools]
202
+ tools = Array(options[:allowed_tools]).join(",")
203
+ cmd_array += ["--allowedTools", tools]
204
+ end
203
205
 
204
- # Add permission prompt tool if not in vibe mode
205
- cmd_array += ["--permission-prompt-tool", "mcp__permissions__check_permission"] unless @vibe
206
+ # Add disallowed tools if any
207
+ if options[:disallowed_tools]
208
+ disallowed_tools = Array(options[:disallowed_tools]).join(",")
209
+ cmd_array += ["--disallowedTools", disallowed_tools]
210
+ end
211
+
212
+ # Add permission prompt tool if not in vibe mode
213
+ cmd_array += ["--permission-prompt-tool", "mcp__permissions__check_permission"]
214
+ end
206
215
 
207
216
  cmd_array
208
217
  end
@@ -20,6 +20,8 @@ module ClaudeSwarm
20
20
  desc: "Run with --dangerously-skip-permissions for all instances"
21
21
  method_option :prompt, aliases: "-p", type: :string,
22
22
  desc: "Prompt to pass to the main Claude instance (non-interactive mode)"
23
+ method_option :stream_logs, type: :boolean, default: false,
24
+ desc: "Stream session logs to stdout (only works with -p)"
23
25
  def start(config_file = nil)
24
26
  config_path = config_file || options[:config]
25
27
  unless File.exist?(config_path)
@@ -28,6 +30,13 @@ module ClaudeSwarm
28
30
  end
29
31
 
30
32
  say "Starting Claude Swarm from #{config_path}..." unless options[:prompt]
33
+
34
+ # Validate stream_logs option
35
+ if options[:stream_logs] && !options[:prompt]
36
+ error "--stream-logs can only be used with -p/--prompt"
37
+ exit 1
38
+ end
39
+
31
40
  begin
32
41
  config = Configuration.new(config_path)
33
42
  session_timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
@@ -35,7 +44,8 @@ module ClaudeSwarm
35
44
  orchestrator = Orchestrator.new(config, generator,
36
45
  vibe: options[:vibe],
37
46
  prompt: options[:prompt],
38
- session_timestamp: session_timestamp)
47
+ session_timestamp: session_timestamp,
48
+ stream_logs: options[:stream_logs])
39
49
  orchestrator.start
40
50
  rescue Error => e
41
51
  error e.message
@@ -60,6 +70,8 @@ module ClaudeSwarm
60
70
  desc: "Description of the instance's role"
61
71
  method_option :tools, aliases: "-t", type: :array,
62
72
  desc: "Allowed tools for the instance"
73
+ method_option :disallowed_tools, type: :array,
74
+ desc: "Disallowed tools for the instance"
63
75
  method_option :mcp_config_path, type: :string,
64
76
  desc: "Path to MCP configuration file"
65
77
  method_option :debug, type: :boolean, default: false,
@@ -76,6 +88,7 @@ module ClaudeSwarm
76
88
  prompt: options[:prompt],
77
89
  description: options[:description],
78
90
  tools: options[:tools] || [],
91
+ disallowed_tools: options[:disallowed_tools] || [],
79
92
  mcp_config_path: options[:mcp_config_path],
80
93
  vibe: options[:vibe]
81
94
  }
@@ -113,7 +126,7 @@ module ClaudeSwarm
113
126
  directory: .
114
127
  model: sonnet
115
128
  prompt: "You are the lead developer coordinating the team"
116
- tools: [Read, Edit, Bash, Write]
129
+ allowed_tools: [Read, Edit, Bash, Write]
117
130
  # connections: [frontend_dev, backend_dev]
118
131
 
119
132
  # Example instances (uncomment and modify as needed):
@@ -123,28 +136,28 @@ module ClaudeSwarm
123
136
  # directory: ./frontend
124
137
  # model: sonnet
125
138
  # prompt: "You specialize in frontend development with React, TypeScript, and modern web technologies"
126
- # tools: [Read, Edit, Write, "Bash(npm:*)", "Bash(yarn:*)", "Bash(pnpm:*)"]
139
+ # allowed_tools: [Read, Edit, Write, "Bash(npm:*)", "Bash(yarn:*)", "Bash(pnpm:*)"]
127
140
 
128
141
  # backend_dev:
129
142
  # description: "Backend developer focusing on APIs, databases, and server architecture"
130
143
  # directory: ../other-app/backend
131
144
  # model: sonnet
132
145
  # prompt: "You specialize in backend development, APIs, databases, and server architecture"
133
- # tools: [Read, Edit, Write, Bash]
146
+ # allowed_tools: [Read, Edit, Write, Bash]
134
147
 
135
148
  # devops_engineer:
136
149
  # description: "DevOps engineer managing infrastructure, CI/CD, and deployments"
137
150
  # directory: .
138
151
  # model: sonnet
139
152
  # prompt: "You specialize in infrastructure, CI/CD, containerization, and deployment"
140
- # tools: [Read, Edit, Write, "Bash(docker:*)", "Bash(kubectl:*)", "Bash(terraform:*)"]
153
+ # allowed_tools: [Read, Edit, Write, "Bash(docker:*)", "Bash(kubectl:*)", "Bash(terraform:*)"]
141
154
 
142
155
  # qa_engineer:
143
156
  # description: "QA engineer ensuring quality through comprehensive testing"
144
157
  # directory: ./tests
145
158
  # model: sonnet
146
159
  # prompt: "You specialize in testing, quality assurance, and test automation"
147
- # tools: [Read, Edit, Write, Bash]
160
+ # allowed_tools: [Read, Edit, Write, Bash]
148
161
  YAML
149
162
 
150
163
  File.write(config_path, template)
@@ -160,10 +173,12 @@ module ClaudeSwarm
160
173
  desc "tools-mcp", "Start a permission management MCP server for tool access control"
161
174
  method_option :allowed_tools, aliases: "-t", type: :array,
162
175
  desc: "Comma-separated list of allowed tool patterns (supports wildcards)"
176
+ method_option :disallowed_tools, type: :array,
177
+ desc: "Comma-separated list of disallowed tool patterns (supports wildcards)"
163
178
  method_option :debug, type: :boolean, default: false,
164
179
  desc: "Enable debug output"
165
180
  def tools_mcp
166
- server = PermissionMcpServer.new(allowed_tools: options[:allowed_tools])
181
+ server = PermissionMcpServer.new(allowed_tools: options[:allowed_tools], disallowed_tools: options[:disallowed_tools])
167
182
  server.start
168
183
  rescue StandardError => e
169
184
  error "Error starting permission MCP server: #{e.message}"
@@ -76,12 +76,17 @@ module ClaudeSwarm
76
76
  # Validate required fields
77
77
  raise Error, "Instance '#{name}' missing required 'description' field" unless config["description"]
78
78
 
79
+ # Support both 'tools' (deprecated) and 'allowed_tools' for backward compatibility
80
+ allowed_tools = config["allowed_tools"] || config["tools"] || []
81
+
79
82
  {
80
83
  name: name,
81
84
  directory: expand_path(config["directory"] || "."),
82
85
  model: config["model"] || "sonnet",
83
86
  connections: Array(config["connections"]),
84
- tools: Array(config["tools"]),
87
+ tools: Array(allowed_tools), # Keep as 'tools' internally for compatibility
88
+ allowed_tools: Array(allowed_tools),
89
+ disallowed_tools: Array(config["disallowed_tools"]),
85
90
  mcps: parse_mcps(config["mcps"] || []),
86
91
  prompt: config["prompt"],
87
92
  description: config["description"],
@@ -59,7 +59,7 @@ module ClaudeSwarm
59
59
  end
60
60
 
61
61
  # Add permission MCP server if not in vibe mode (global or instance-specific)
62
- mcp_servers["permissions"] = build_permission_mcp_config(instance[:tools]) unless @vibe || instance[:vibe]
62
+ mcp_servers["permissions"] = build_permission_mcp_config(instance[:tools], instance[:disallowed_tools]) unless @vibe || instance[:vibe]
63
63
 
64
64
  config = {
65
65
  "mcpServers" => mcp_servers
@@ -105,6 +105,8 @@ module ClaudeSwarm
105
105
 
106
106
  args.push("--tools", instance[:tools].join(",")) if instance[:tools] && !instance[:tools].empty?
107
107
 
108
+ args.push("--disallowed-tools", instance[:disallowed_tools].join(",")) if instance[:disallowed_tools] && !instance[:disallowed_tools].empty?
109
+
108
110
  args.push("--mcp-config-path", mcp_config_path(name))
109
111
 
110
112
  args.push("--calling-instance", calling_instance) if calling_instance
@@ -118,7 +120,7 @@ module ClaudeSwarm
118
120
  }
119
121
  end
120
122
 
121
- def build_permission_mcp_config(allowed_tools)
123
+ def build_permission_mcp_config(allowed_tools, disallowed_tools)
122
124
  exe_path = "claude-swarm"
123
125
 
124
126
  args = ["tools-mcp"]
@@ -126,6 +128,9 @@ module ClaudeSwarm
126
128
  # Add allowed tools if specified
127
129
  args.push("--allowed-tools", allowed_tools.join(",")) if allowed_tools && !allowed_tools.empty?
128
130
 
131
+ # Add disallowed tools if specified
132
+ args.push("--disallowed-tools", disallowed_tools.join(",")) if disallowed_tools && !disallowed_tools.empty?
133
+
129
134
  {
130
135
  "type" => "stdio",
131
136
  "command" => exe_path,
@@ -4,12 +4,13 @@ require "shellwords"
4
4
 
5
5
  module ClaudeSwarm
6
6
  class Orchestrator
7
- def initialize(configuration, mcp_generator, vibe: false, prompt: nil, session_timestamp: nil)
7
+ def initialize(configuration, mcp_generator, vibe: false, prompt: nil, session_timestamp: nil, stream_logs: false)
8
8
  @config = configuration
9
9
  @generator = mcp_generator
10
10
  @vibe = vibe
11
11
  @prompt = prompt
12
12
  @session_timestamp = session_timestamp || Time.now.strftime("%Y%m%d_%H%M%S")
13
+ @stream_logs = stream_logs
13
14
  end
14
15
 
15
16
  def start
@@ -39,7 +40,8 @@ module ClaudeSwarm
39
40
  puts "🚀 Launching main instance: #{@config.main_instance}"
40
41
  puts " Model: #{main_instance[:model]}"
41
42
  puts " Directory: #{main_instance[:directory]}"
42
- puts " Tools: #{main_instance[:tools].join(", ")}" if main_instance[:tools].any?
43
+ puts " Allowed tools: #{main_instance[:tools].join(", ")}" if main_instance[:tools].any?
44
+ puts " Disallowed tools: #{main_instance[:disallowed_tools].join(", ")}" if main_instance[:disallowed_tools]&.any?
43
45
  puts " Connections: #{main_instance[:connections].join(", ")}" if main_instance[:connections].any?
44
46
  puts " 😎 Vibe mode ON for this instance" if main_instance[:vibe]
45
47
  puts
@@ -51,14 +53,52 @@ module ClaudeSwarm
51
53
  puts
52
54
  end
53
55
 
56
+ # Start log streaming thread if in non-interactive mode with --stream-logs
57
+ log_thread = nil
58
+ log_thread = start_log_streaming if @prompt && @stream_logs
59
+
54
60
  # Execute the main instance - this will cascade to other instances via MCP
55
61
  Dir.chdir(main_instance[:directory]) do
56
62
  system(*command)
57
63
  end
64
+
65
+ # Clean up log streaming thread
66
+ return unless log_thread
67
+
68
+ log_thread.terminate
69
+ log_thread.join
58
70
  end
59
71
 
60
72
  private
61
73
 
74
+ def start_log_streaming
75
+ Thread.new do
76
+ session_log_path = File.join(Dir.pwd, ClaudeSwarm::ClaudeCodeExecutor::SWARM_DIR,
77
+ ClaudeSwarm::ClaudeCodeExecutor::SESSIONS_DIR,
78
+ @session_timestamp, "session.log")
79
+
80
+ # Wait for log file to be created
81
+ sleep 0.1 until File.exist?(session_log_path)
82
+
83
+ # Open file and seek to end
84
+ File.open(session_log_path, "r") do |file|
85
+ file.seek(0, IO::SEEK_END)
86
+
87
+ loop do
88
+ changes = file.read
89
+ if changes
90
+ print changes
91
+ $stdout.flush
92
+ else
93
+ sleep 0.1
94
+ end
95
+ end
96
+ end
97
+ rescue StandardError
98
+ # Silently handle errors (file might be deleted, process might end, etc.)
99
+ end
100
+ end
101
+
62
102
  def build_main_command(instance)
63
103
  parts = [
64
104
  "claude",
@@ -68,11 +108,22 @@ module ClaudeSwarm
68
108
 
69
109
  if @vibe || instance[:vibe]
70
110
  parts << "--dangerously-skip-permissions"
71
- elsif instance[:tools].any?
72
- tools_str = instance[:tools].join(",")
73
- parts << "--allowedTools"
74
- parts << tools_str
75
- # Add permission prompt tool
111
+ else
112
+ # Add allowed tools if any
113
+ if instance[:tools].any?
114
+ tools_str = instance[:tools].join(",")
115
+ parts << "--allowedTools"
116
+ parts << tools_str
117
+ end
118
+
119
+ # Add disallowed tools if any
120
+ if instance[:disallowed_tools]&.any?
121
+ disallowed_tools_str = instance[:disallowed_tools].join(",")
122
+ parts << "--disallowedTools"
123
+ parts << disallowed_tools_str
124
+ end
125
+
126
+ # Add permission prompt tool unless in vibe mode
76
127
  parts << "--permission-prompt-tool"
77
128
  parts << "mcp__permissions__check_permission"
78
129
  end
@@ -11,19 +11,23 @@ module ClaudeSwarm
11
11
  SWARM_DIR = ".claude-swarm"
12
12
  SESSIONS_DIR = "sessions"
13
13
 
14
- def initialize(allowed_tools: nil)
14
+ def initialize(allowed_tools: nil, disallowed_tools: nil)
15
15
  @allowed_tools = allowed_tools
16
+ @disallowed_tools = disallowed_tools
16
17
  setup_logging
17
18
  end
18
19
 
19
20
  def start
20
- # Parse allowed tools
21
- allowed_patterns = parse_allowed_tools(@allowed_tools)
21
+ # Parse allowed and disallowed tools
22
+ allowed_patterns = parse_tool_patterns(@allowed_tools)
23
+ disallowed_patterns = parse_tool_patterns(@disallowed_tools)
22
24
 
23
- @logger.info("Starting permission MCP server with allowed patterns: #{allowed_patterns.inspect}")
25
+ @logger.info("Starting permission MCP server with allowed patterns: #{allowed_patterns.inspect}, " \
26
+ "disallowed patterns: #{disallowed_patterns.inspect}")
24
27
 
25
28
  # Set the patterns on the tool class
26
29
  PermissionTool.allowed_patterns = allowed_patterns
30
+ PermissionTool.disallowed_patterns = disallowed_patterns
27
31
  PermissionTool.logger = @logger
28
32
 
29
33
  server = FastMcp::Server.new(
@@ -64,7 +68,7 @@ module ClaudeSwarm
64
68
  @logger.info("Permission MCP server logging initialized")
65
69
  end
66
70
 
67
- def parse_allowed_tools(tools)
71
+ def parse_tool_patterns(tools)
68
72
  return [] if tools.nil? || tools.empty?
69
73
 
70
74
  # Handle both string and array inputs
@@ -5,9 +5,9 @@ require "fast_mcp"
5
5
 
6
6
  module ClaudeSwarm
7
7
  class PermissionTool < FastMcp::Tool
8
- # Class variables to store allowed patterns and logger
8
+ # Class variables to store allowed/disallowed patterns and logger
9
9
  class << self
10
- attr_accessor :allowed_patterns, :logger
10
+ attr_accessor :allowed_patterns, :disallowed_patterns, :logger
11
11
  end
12
12
 
13
13
  tool_name "check_permission"
@@ -23,41 +23,65 @@ module ClaudeSwarm
23
23
  logger.info("Permission check requested for tool: #{tool_name}")
24
24
  logger.info("Tool input: #{input.inspect}")
25
25
 
26
- # Check if the tool matches any allowed pattern
27
- patterns = self.class.allowed_patterns || []
28
- logger.info("Checking against patterns: #{patterns.inspect}")
29
-
30
- allowed = patterns.any? do |pattern|
31
- match = if pattern.include?("*")
32
- # Convert wildcard pattern to regex
33
- regex_pattern = pattern.gsub("*", ".*")
34
- tool_name.match?(/^#{regex_pattern}$/)
35
- else
36
- # Exact match
37
- tool_name == pattern
38
- end
39
- logger.info("Pattern '#{pattern}' vs '#{tool_name}': #{match}")
26
+ allowed_patterns = self.class.allowed_patterns || []
27
+ disallowed_patterns = self.class.disallowed_patterns || []
28
+
29
+ logger.info("Checking against allowed patterns: #{allowed_patterns.inspect}")
30
+ logger.info("Checking against disallowed patterns: #{disallowed_patterns.inspect}")
31
+
32
+ # Check if tool matches any disallowed pattern first (takes precedence)
33
+ disallowed = disallowed_patterns.any? do |pattern|
34
+ match = matches_pattern?(tool_name, pattern)
35
+ logger.info("Disallowed pattern '#{pattern}' vs '#{tool_name}': #{match}")
40
36
  match
41
37
  end
42
38
 
43
- result = if allowed
44
- logger.info("ALLOWED: Tool '#{tool_name}' matches configured patterns")
45
- {
46
- "behavior" => "allow",
47
- "updatedInput" => input
48
- }
49
- else
50
- logger.info("DENIED: Tool '#{tool_name}' does not match any configured patterns")
51
- {
52
- "behavior" => "deny",
53
- "message" => "Tool '#{tool_name}' is not allowed by configured patterns"
54
- }
55
- end
39
+ if disallowed
40
+ logger.info("DENIED: Tool '#{tool_name}' matches disallowed pattern")
41
+ result = {
42
+ "behavior" => "deny",
43
+ "message" => "Tool '#{tool_name}' is explicitly disallowed"
44
+ }
45
+ else
46
+ # Check if the tool matches any allowed pattern
47
+ allowed = allowed_patterns.empty? || allowed_patterns.any? do |pattern|
48
+ match = matches_pattern?(tool_name, pattern)
49
+ logger.info("Allowed pattern '#{pattern}' vs '#{tool_name}': #{match}")
50
+ match
51
+ end
52
+
53
+ result = if allowed
54
+ logger.info("ALLOWED: Tool '#{tool_name}' matches configured patterns")
55
+ {
56
+ "behavior" => "allow",
57
+ "updatedInput" => input
58
+ }
59
+ else
60
+ logger.info("DENIED: Tool '#{tool_name}' does not match any allowed patterns")
61
+ {
62
+ "behavior" => "deny",
63
+ "message" => "Tool '#{tool_name}' is not allowed by configured patterns"
64
+ }
65
+ end
66
+ end
56
67
 
57
68
  # Return JSON-stringified result as per SDK docs
58
69
  response = JSON.generate(result)
59
70
  logger.info("Returning response: #{response}")
60
71
  response
61
72
  end
73
+
74
+ private
75
+
76
+ def matches_pattern?(tool_name, pattern)
77
+ if pattern.include?("*")
78
+ # Convert wildcard pattern to regex
79
+ regex_pattern = pattern.gsub("*", ".*")
80
+ tool_name.match?(/^#{regex_pattern}$/)
81
+ else
82
+ # Exact match
83
+ tool_name == pattern
84
+ end
85
+ end
62
86
  end
63
87
  end
@@ -23,6 +23,9 @@ module ClaudeSwarm
23
23
  # Add allowed tools from instance config
24
24
  options[:allowed_tools] = instance_config[:tools] if instance_config[:tools]&.any?
25
25
 
26
+ # Add disallowed tools from instance config
27
+ options[:disallowed_tools] = instance_config[:disallowed_tools] if instance_config[:disallowed_tools]&.any?
28
+
26
29
  response = executor.execute(prompt, options)
27
30
 
28
31
  # Return just the result text as expected by MCP
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeSwarm
4
- VERSION = "0.1.7"
4
+ VERSION = "0.1.8"
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.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Arruda
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-06-03 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: thor
@@ -56,8 +56,8 @@ files:
56
56
  - CLAUDE.md
57
57
  - LICENSE
58
58
  - README.md
59
+ - RELEASING.md
59
60
  - Rakefile
60
- - claude-swarm.yml
61
61
  - example/claude-swarm.yml
62
62
  - exe/claude-swarm
63
63
  - lib/claude_swarm.rb
@@ -87,14 +87,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
87
87
  requirements:
88
88
  - - ">="
89
89
  - !ruby/object:Gem::Version
90
- version: 3.1.0
90
+ version: 3.2.0
91
91
  required_rubygems_version: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - ">="
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0'
96
96
  requirements: []
97
- rubygems_version: 3.6.2
97
+ rubygems_version: 3.6.7
98
98
  specification_version: 4
99
99
  summary: Orchestrate multiple Claude Code instances as a collaborative AI development
100
100
  team
data/claude-swarm.yml DELETED
@@ -1,26 +0,0 @@
1
- version: 1
2
- swarm:
3
- name: "Swarm Name"
4
- main: lead_developer
5
- instances:
6
- lead_developer:
7
- description: "Lead developer coordinating the team and making architectural decisions"
8
- directory: .
9
- model: opus
10
- prompt: "You are the lead developer coordinating the team"
11
- vibe: true
12
- connections: [frontend_dev]
13
-
14
- # Example instances (uncomment and modify as needed):
15
-
16
- frontend_dev:
17
- description: "Frontend developer specializing in React and modern web technologies"
18
- directory: .
19
- model: opus
20
- prompt: "You specialize in frontend development with React, TypeScript, and modern web technologies"
21
- tools: [mcp__headless_browser__*]
22
- mcps:
23
- - name: headless_browser
24
- type: stdio
25
- command: bundle
26
- args: ["exec", "hbt", "stdio"]