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 +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +57 -30
- data/RELEASING.md +110 -0
- data/lib/claude_swarm/claude_code_executor.rb +15 -6
- data/lib/claude_swarm/cli.rb +22 -7
- data/lib/claude_swarm/configuration.rb +6 -1
- data/lib/claude_swarm/mcp_generator.rb +7 -2
- data/lib/claude_swarm/orchestrator.rb +58 -7
- data/lib/claude_swarm/permission_mcp_server.rb +9 -5
- data/lib/claude_swarm/permission_tool.rb +53 -29
- data/lib/claude_swarm/task_tool.rb +3 -0
- data/lib/claude_swarm/version.rb +1 -1
- metadata +5 -5
- data/claude-swarm.yml +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4694bdf654adfb2784e4156d504ace610c9b48bd7aedafb4d5571ee0483325e6
|
4
|
+
data.tar.gz: 9f26361aac4c2815b11f95ab28a67fbb684377d691b7ee5a1b99c89a1a4fa3c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
+
[](https://badge.fury.io/rb/claude_swarm)
|
4
|
+
[](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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
- **
|
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
|
-
|
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
|
-
|
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`
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
200
|
-
tools
|
201
|
-
|
202
|
-
|
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
|
-
|
205
|
-
|
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
|
data/lib/claude_swarm/cli.rb
CHANGED
@@ -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
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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(
|
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 "
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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 =
|
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
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
data/lib/claude_swarm/version.rb
CHANGED
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
|
+
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:
|
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.
|
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.
|
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"]
|