bullswarm 0.1.4
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.
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/bin/bullswarm.js +12 -0
- package/connectors/_schema.json +31 -0
- package/connectors/claude-code.json +17 -0
- package/connectors/codex.json +41 -0
- package/connectors/command-code.json +39 -0
- package/connectors/echo-worker.mjs +38 -0
- package/connectors/echo.json +16 -0
- package/connectors/grok.json +38 -0
- package/connectors/opencode2.json +18 -0
- package/mcp/server.mjs +138 -0
- package/package.json +48 -0
- package/skill/SKILL.md +53 -0
- package/src/cli.js +307 -0
- package/src/lib/config.js +107 -0
- package/src/lib/release.js +55 -0
- package/src/lib/route.js +133 -0
- package/src/lib/state.js +105 -0
- package/src/lib/verify.js +126 -0
- package/src/lib/version.js +17 -0
- package/src/lib/watch.js +167 -0
- package/src/meters/claude.js +126 -0
- package/src/meters/codex.js +264 -0
- package/src/meters/command-code.js +218 -0
- package/src/meters/framework.js +128 -0
- package/src/meters/grok.js +200 -0
- package/src/meters/registry.js +85 -0
- package/src/setup.js +272 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 bullswarm contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# bullswarm
|
|
2
|
+
|
|
3
|
+
Route work across coding-agent CLIs. The frontier agent keeps orchestration,
|
|
4
|
+
judgment, and synthesis; bounded work goes to whichever subscription has the
|
|
5
|
+
most quota left; every delegate output is judged by content before it counts.
|
|
6
|
+
|
|
7
|
+
## The doctrine (non-negotiable)
|
|
8
|
+
|
|
9
|
+
1. **Judge by CONTENT, not exit code.** Every delegate CLI can exit 0 while
|
|
10
|
+
having done nothing. A non-zero exit is never a success; `ok:true` requires
|
|
11
|
+
passing verification.
|
|
12
|
+
2. **Pace by meter.** The scheduling resource is the subscription window:
|
|
13
|
+
elapsed% minus used%, most-behind pool wins. Pace may only promote a
|
|
14
|
+
*cheaper* pool. Lanes are work-nature, never hard-coded to pools.
|
|
15
|
+
3. **Delegate output is input, never the answer.** Final synthesis,
|
|
16
|
+
architecture decisions, and anything needing live conversation context
|
|
17
|
+
stays with the caller.
|
|
18
|
+
4. **Quarantine re-probes.** A benched pool must be able to return to service
|
|
19
|
+
automatically; a lane is never allowed to silently go down.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g bullswarm # or: node bin/bullswarm.js directly from a checkout
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bullswarm # first run: interactive setup wizard
|
|
31
|
+
bullswarm setup # re-run or repair
|
|
32
|
+
bullswarm pools # meter state, pace position, quarantine status
|
|
33
|
+
bullswarm run --lane analyze --add-dir ~/some-repo --task-file /tmp/t.md --json
|
|
34
|
+
bullswarm health # re-judge saved outputs; catch gate failures
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Verbs
|
|
38
|
+
|
|
39
|
+
| Verb | Purpose |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `setup` | Discover installed agent CLIs, show quota state, toggle pools, suggest a routing table, write config. Approval-gated, idempotent. |
|
|
42
|
+
| `run` | route → dispatch → watch → verify → one JSON verdict |
|
|
43
|
+
| `health` | Re-judge saved outputs against their verdicts; surface verify-gate failures and quarantine clusters |
|
|
44
|
+
| `pools` | Show each pool's meter state, pace position, quarantine status |
|
|
45
|
+
|
|
46
|
+
## The verdict
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"ok": true,
|
|
51
|
+
"keepOnClaude": false,
|
|
52
|
+
"why": "verified",
|
|
53
|
+
"pick": { "pool": "grok", "command": ["grok", "-p", "..."] },
|
|
54
|
+
"contentUsableDespiteExit": false,
|
|
55
|
+
"outFile": "/tmp/dlg.out"
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- `ok: true` — verified output, read the file
|
|
60
|
+
- `keepOnClaude: true` — router says do it in-session; nothing ran
|
|
61
|
+
- `ok: false` — `why` names the failed gate
|
|
62
|
+
- `contentUsableDespiteExit: true` — non-zero exit but complete output; read
|
|
63
|
+
before re-running
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
MIT
|
package/bin/bullswarm.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// bullswarm — route work across coding-agent CLI subscriptions.
|
|
3
|
+
|
|
4
|
+
import { main } from '../src/cli.js';
|
|
5
|
+
|
|
6
|
+
main(process.argv.slice(2)).then(
|
|
7
|
+
(code) => process.exit(code),
|
|
8
|
+
(err) => {
|
|
9
|
+
console.error(err?.message ?? err);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
},
|
|
12
|
+
);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Connector schema. Every per-CLI quirk lives HERE, never in core logic.",
|
|
3
|
+
"name": "connector-name (matches pool name)",
|
|
4
|
+
"bin": "binary on PATH to probe for discovery",
|
|
5
|
+
"configDirs": ["~/.example", "~/.config/example"],
|
|
6
|
+
"spawn": {
|
|
7
|
+
"$comment": "argv template; {taskFile} {cwd} {outFile} are substituted",
|
|
8
|
+
"cmd": ["example-cli", "--task", "{taskFile}"],
|
|
9
|
+
"cwdMode": "add-dir|task-file-dir",
|
|
10
|
+
"$comment-cwd": "cwdMode documents how the CLI resolves its project: 'pwd' means the CLI resolves from $PWD so the watcher MUST set PWD and spawn inside --add-dir"
|
|
11
|
+
},
|
|
12
|
+
"authSignatures": ["strings in output that mean auth/throttle failure"],
|
|
13
|
+
"outputExtraction": {
|
|
14
|
+
"$comment": "how to get the real answer out of stdout+stderr+files",
|
|
15
|
+
"strategy": "stdout|stdout-tail|json-field|file",
|
|
16
|
+
"field": "optional json field path or file glob"
|
|
17
|
+
},
|
|
18
|
+
"meter": {
|
|
19
|
+
"$comment": "none | declared (manual) | reader (programmatic)",
|
|
20
|
+
"type": "none|declared|reader",
|
|
21
|
+
"window": "5h|weekly|none (for declared/reader)",
|
|
22
|
+
"readerCmd": "optional command emitting usedPct as JSON"
|
|
23
|
+
},
|
|
24
|
+
"costRank": 2,
|
|
25
|
+
"lanes": ["analyze", "build", "chore"],
|
|
26
|
+
"flags": {
|
|
27
|
+
"stealth": false,
|
|
28
|
+
"$comment-stealth": "stealth=true: prompts/completions retained by an anonymous provider — opt-in only, never a default"
|
|
29
|
+
},
|
|
30
|
+
"timeoutSec": 900
|
|
31
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-code",
|
|
3
|
+
"bin": "claude",
|
|
4
|
+
"configDirs": ["~/.claude"],
|
|
5
|
+
"spawn": {
|
|
6
|
+
"cmd": ["claude", "-p", "Read the file {taskFile} and follow its instructions exactly.", "--add-dir", "{cwd}", "--no-session"],
|
|
7
|
+
"cwdMode": "add-dir"
|
|
8
|
+
},
|
|
9
|
+
"authSignatures": ["unauthorized", "authentication failed", "credit balance"],
|
|
10
|
+
"outputExtraction": { "strategy": "stdout" },
|
|
11
|
+
"$comment-meter": "the CALLER pool — competes in analyze/build, wins only when no delegate can take the lane",
|
|
12
|
+
"meter": { "type": "reader", "window": "weekly+5h" },
|
|
13
|
+
"costRank": 4,
|
|
14
|
+
"lanes": ["analyze", "build", "chore"],
|
|
15
|
+
"flags": { "stealth": false },
|
|
16
|
+
"timeoutSec": 900
|
|
17
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codex",
|
|
3
|
+
"bin": "codex",
|
|
4
|
+
"configDirs": [
|
|
5
|
+
"~/.codex"
|
|
6
|
+
],
|
|
7
|
+
"spawn": {
|
|
8
|
+
"cmd": [
|
|
9
|
+
"codex",
|
|
10
|
+
"exec",
|
|
11
|
+
"--skip-git-repo-check",
|
|
12
|
+
"--add-dir",
|
|
13
|
+
"{cwd}",
|
|
14
|
+
"Read {taskFile} and follow the instructions."
|
|
15
|
+
],
|
|
16
|
+
"cwdMode": "add-dir",
|
|
17
|
+
"$comment-argv": "{cwd} is substituted by the watcher with the resolved target directory"
|
|
18
|
+
},
|
|
19
|
+
"authSignatures": [
|
|
20
|
+
"usage_credits_required",
|
|
21
|
+
"unauthorized",
|
|
22
|
+
"invalid api key"
|
|
23
|
+
],
|
|
24
|
+
"outputExtraction": {
|
|
25
|
+
"strategy": "stdout"
|
|
26
|
+
},
|
|
27
|
+
"meter": {
|
|
28
|
+
"type": "reader",
|
|
29
|
+
"window": "weekly"
|
|
30
|
+
},
|
|
31
|
+
"costRank": 3,
|
|
32
|
+
"lanes": [
|
|
33
|
+
"analyze",
|
|
34
|
+
"build",
|
|
35
|
+
"chore"
|
|
36
|
+
],
|
|
37
|
+
"flags": {
|
|
38
|
+
"stealth": false
|
|
39
|
+
},
|
|
40
|
+
"timeoutSec": 900
|
|
41
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "command-code",
|
|
3
|
+
"bin": "command-code",
|
|
4
|
+
"configDirs": [
|
|
5
|
+
"~/.commandcode"
|
|
6
|
+
],
|
|
7
|
+
"spawn": {
|
|
8
|
+
"cmd": [
|
|
9
|
+
"command-code",
|
|
10
|
+
"-p",
|
|
11
|
+
"--no-session",
|
|
12
|
+
"--tools-all",
|
|
13
|
+
"--yolo",
|
|
14
|
+
"Read the file {taskFile} and follow its instructions exactly."
|
|
15
|
+
],
|
|
16
|
+
"cwdMode": "add-dir"
|
|
17
|
+
},
|
|
18
|
+
"authSignatures": [
|
|
19
|
+
"unauthorized",
|
|
20
|
+
"authentication failed"
|
|
21
|
+
],
|
|
22
|
+
"outputExtraction": {
|
|
23
|
+
"strategy": "stdout"
|
|
24
|
+
},
|
|
25
|
+
"meter": {
|
|
26
|
+
"type": "reader",
|
|
27
|
+
"window": "weekly+monthly+5h"
|
|
28
|
+
},
|
|
29
|
+
"costRank": 1,
|
|
30
|
+
"lanes": [
|
|
31
|
+
"build",
|
|
32
|
+
"chore"
|
|
33
|
+
],
|
|
34
|
+
"$comment-lanes": "cannot serve analyze (upstream 403 on the frontier model)",
|
|
35
|
+
"flags": {
|
|
36
|
+
"stealth": false
|
|
37
|
+
},
|
|
38
|
+
"timeoutSec": 900
|
|
39
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// echo-worker.mjs — deterministic test delegate for bullswarm.
|
|
2
|
+
// Reads a task file; behavior is driven by directives in the task text.
|
|
3
|
+
// FAIL:auth -> prints an auth failure, exits 0 (the lying-exit trap)
|
|
4
|
+
// FAIL:exit -> prints a complete answer, exits 1 (exit-1-after-success)
|
|
5
|
+
// INTENT: -> prints only an announcement, exits 0
|
|
6
|
+
// otherwise -> echoes the task as a completed answer, exit 0
|
|
7
|
+
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
9
|
+
|
|
10
|
+
const task = readFileSync(process.argv[2], 'utf8');
|
|
11
|
+
|
|
12
|
+
if (task.includes('FAIL:auth')) {
|
|
13
|
+
console.log('Authentication failed: no credentials found in keychain.');
|
|
14
|
+
process.exit(0);
|
|
15
|
+
}
|
|
16
|
+
if (task.includes('FAIL:exit')) {
|
|
17
|
+
console.log(
|
|
18
|
+
'Refactor complete.\n\n- Renamed getUser to fetchUser across 12 files (grep-verified: zero remaining references).\n- All 47 tests pass. Files touched: src/api/user.ts, src/api/index.ts, src/hooks/useUser.ts, and 9 test files.',
|
|
19
|
+
);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
if (task.includes('INTENT:')) {
|
|
23
|
+
console.log(
|
|
24
|
+
"I'll inspect log.ts and tail.ts, then trace the rotation path through the config loader. I'll report back with the root cause.",
|
|
25
|
+
);
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
if (task.includes('PWD:')) {
|
|
29
|
+
console.log(
|
|
30
|
+
`## Working directory report\n\nThe delegate process resolved its project context from the following locations, captured immediately at spawn time:\n\n- PWD environment variable: ${process.env.PWD ?? '(unset)'}\n- getcwd() / process.cwd(): ${process.cwd()}\n\nBoth values agree, confirming the launcher set the working directory correctly for this run.`,
|
|
31
|
+
);
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log(
|
|
36
|
+
`## Completed\n\nProcessed the task file successfully.\n\n- Read and executed every directive found in ${process.argv[2]}.\n- Verified the output directory exists and is writable before writing.\n- Ran the full local validation suite: all checks passed with exit code 0.\n\nNo errors were encountered during the run.`,
|
|
37
|
+
);
|
|
38
|
+
process.exit(0);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "echo",
|
|
3
|
+
"bin": "node",
|
|
4
|
+
"configDirs": [],
|
|
5
|
+
"spawn": {
|
|
6
|
+
"cmd": ["node", "{bullswarmDir}/connectors/echo-worker.mjs", "{taskFile}"],
|
|
7
|
+
"cwdMode": "task-file-dir"
|
|
8
|
+
},
|
|
9
|
+
"authSignatures": ["Authentication failed", "unauthorized"],
|
|
10
|
+
"outputExtraction": { "strategy": "stdout" },
|
|
11
|
+
"meter": { "type": "none" },
|
|
12
|
+
"costRank": 5,
|
|
13
|
+
"lanes": ["analyze", "build", "chore"],
|
|
14
|
+
"flags": { "stealth": false },
|
|
15
|
+
"timeoutSec": 120
|
|
16
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "grok",
|
|
3
|
+
"bin": "grok",
|
|
4
|
+
"configDirs": [
|
|
5
|
+
"~/.grok"
|
|
6
|
+
],
|
|
7
|
+
"spawn": {
|
|
8
|
+
"cmd": [
|
|
9
|
+
"grok",
|
|
10
|
+
"-p",
|
|
11
|
+
"{taskFile}"
|
|
12
|
+
],
|
|
13
|
+
"cwdMode": "add-dir"
|
|
14
|
+
},
|
|
15
|
+
"authSignatures": [
|
|
16
|
+
"unauthorized",
|
|
17
|
+
"rate limit",
|
|
18
|
+
"invalid api key"
|
|
19
|
+
],
|
|
20
|
+
"outputExtraction": {
|
|
21
|
+
"strategy": "stdout"
|
|
22
|
+
},
|
|
23
|
+
"meter": {
|
|
24
|
+
"type": "reader",
|
|
25
|
+
"window": "weekly"
|
|
26
|
+
},
|
|
27
|
+
"costRank": 2,
|
|
28
|
+
"lanes": [
|
|
29
|
+
"analyze",
|
|
30
|
+
"build",
|
|
31
|
+
"chore"
|
|
32
|
+
],
|
|
33
|
+
"flags": {
|
|
34
|
+
"stealth": false
|
|
35
|
+
},
|
|
36
|
+
"timeoutSec": 900,
|
|
37
|
+
"$comment-meter": "weekly shared credit pool via billing endpoint; no 5h window exists on unified-billing accounts"
|
|
38
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode2",
|
|
3
|
+
"bin": "opencode",
|
|
4
|
+
"configDirs": ["~/.config/opencode"],
|
|
5
|
+
"spawn": {
|
|
6
|
+
"cmd": ["opencode", "run", "{taskFile}"],
|
|
7
|
+
"cwdMode": "pwd",
|
|
8
|
+
"$comment-cwdMode": "QUIRK: resolves its project from $PWD, not the spawn cwd. The watcher MUST set env.PWD and spawn with cwd inside the target repo, or it will silently analyse the wrong repository and answer confidently about it."
|
|
9
|
+
},
|
|
10
|
+
"authSignatures": ["No cookie auth credentials found", "unauthorized"],
|
|
11
|
+
"outputExtraction": { "strategy": "stdout" },
|
|
12
|
+
"$comment-exit1": "known failure mode: writes a complete correct answer, then dies with a Console-sync auth error and exit 1. The verdict sets contentUsableDespiteExit instead of discarding the work.",
|
|
13
|
+
"meter": { "type": "none" },
|
|
14
|
+
"costRank": 1,
|
|
15
|
+
"lanes": ["analyze", "build", "chore"],
|
|
16
|
+
"flags": { "stealth": false },
|
|
17
|
+
"timeoutSec": 900
|
|
18
|
+
}
|
package/mcp/server.mjs
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// bullswarm MCP server — exposes run/health/pools over stdio JSON-RPC 2.0.
|
|
3
|
+
// One implementation; every MCP client (Claude Code, Codex, Cursor, …)
|
|
4
|
+
// can offload without shell plumbing.
|
|
5
|
+
|
|
6
|
+
import { createInterface } from 'node:readline';
|
|
7
|
+
import { main } from '../src/cli.js';
|
|
8
|
+
import { getVersion } from '../src/lib/version.js';
|
|
9
|
+
|
|
10
|
+
const VERSION = getVersion();
|
|
11
|
+
|
|
12
|
+
const TOOLS = [
|
|
13
|
+
{
|
|
14
|
+
name: 'bullswarm_run',
|
|
15
|
+
description:
|
|
16
|
+
'Offload a task to the best available coding-agent pool, routed by quota pace and verified by content. Returns a verdict: ok=true means read outFile; keepOnClaude=true means do it in-session; ok=false means the why field names the failed gate.',
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
lane: { type: 'string', enum: ['analyze', 'build', 'chore'] },
|
|
21
|
+
task: { type: 'string', description: 'The task text to delegate.' },
|
|
22
|
+
addDir: { type: 'string', description: 'Target repository directory.' },
|
|
23
|
+
timeout: { type: 'number' },
|
|
24
|
+
},
|
|
25
|
+
required: ['lane', 'task'],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'bullswarm_health',
|
|
30
|
+
description:
|
|
31
|
+
'Re-judge saved offload outputs against their verdicts; report verify-gate failures and quarantine clusters. Run after every offload round.',
|
|
32
|
+
inputSchema: { type: 'object', properties: {} },
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'bullswarm_pools',
|
|
36
|
+
description: 'Show each pool meter state, pace position, and quarantine status.',
|
|
37
|
+
inputSchema: { type: 'object', properties: {} },
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
function write(msg) {
|
|
42
|
+
process.stdout.write(`${JSON.stringify(msg)}\n`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function result(id, r) {
|
|
46
|
+
write({ jsonrpc: '2.0', id, result: r });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function callTool(name, args) {
|
|
50
|
+
// Reuse the CLI verbs but capture stdout instead of leaking to our protocol
|
|
51
|
+
// stream: swap console.log for the duration of the call.
|
|
52
|
+
const chunks = [];
|
|
53
|
+
const origLog = console.log;
|
|
54
|
+
const origErr = console.error;
|
|
55
|
+
console.log = (...a) => chunks.push(a.join(' '));
|
|
56
|
+
console.error = () => {};
|
|
57
|
+
try {
|
|
58
|
+
const argv =
|
|
59
|
+
name === 'bullswarm_run'
|
|
60
|
+
? [
|
|
61
|
+
'run',
|
|
62
|
+
'--lane',
|
|
63
|
+
args.lane,
|
|
64
|
+
'--json',
|
|
65
|
+
...(args.addDir ? ['--add-dir', args.addDir] : []),
|
|
66
|
+
...(args.timeout ? ['--timeout', String(args.timeout)] : []),
|
|
67
|
+
...args.task.split(' '),
|
|
68
|
+
]
|
|
69
|
+
: name === 'bullswarm_health'
|
|
70
|
+
? ['health']
|
|
71
|
+
: ['pools', '--json'];
|
|
72
|
+
const code = await main(argv);
|
|
73
|
+
let parsed = null;
|
|
74
|
+
try {
|
|
75
|
+
parsed = JSON.parse(chunks.join('\n'));
|
|
76
|
+
} catch {
|
|
77
|
+
parsed = chunks.join('\n');
|
|
78
|
+
}
|
|
79
|
+
return { content: [{ type: 'text', text: JSON.stringify({ exitCode: code, verdict: parsed }, null, 2) }] };
|
|
80
|
+
} finally {
|
|
81
|
+
console.log = origLog;
|
|
82
|
+
console.error = origErr;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function handleMessage(line) {
|
|
87
|
+
if (!line.trim()) return;
|
|
88
|
+
let msg;
|
|
89
|
+
try {
|
|
90
|
+
msg = JSON.parse(line);
|
|
91
|
+
} catch {
|
|
92
|
+
write({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const { id, method, params } = msg;
|
|
96
|
+
|
|
97
|
+
switch (method) {
|
|
98
|
+
case 'initialize':
|
|
99
|
+
result(id, {
|
|
100
|
+
protocolVersion: '2024-11-05',
|
|
101
|
+
capabilities: { tools: {} },
|
|
102
|
+
serverInfo: { name: 'bullswarm', version: VERSION },
|
|
103
|
+
});
|
|
104
|
+
break;
|
|
105
|
+
case 'notifications/initialized':
|
|
106
|
+
break; // no response for notifications
|
|
107
|
+
case 'tools/list':
|
|
108
|
+
result(id, { tools: TOOLS });
|
|
109
|
+
break;
|
|
110
|
+
case 'tools/call':
|
|
111
|
+
callTool(params.name, params.arguments ?? {})
|
|
112
|
+
.then((r) => id != null && result(id, r))
|
|
113
|
+
.catch((err) =>
|
|
114
|
+
id != null &&
|
|
115
|
+
write({
|
|
116
|
+
jsonrpc: '2.0',
|
|
117
|
+
id,
|
|
118
|
+
error: { code: -32603, message: err?.message ?? 'internal error' },
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
break;
|
|
122
|
+
case 'ping':
|
|
123
|
+
result(id, {});
|
|
124
|
+
break;
|
|
125
|
+
default:
|
|
126
|
+
if (id != null) {
|
|
127
|
+
write({
|
|
128
|
+
jsonrpc: '2.0',
|
|
129
|
+
id,
|
|
130
|
+
error: { code: -32601, message: `Method not found: ${method}` },
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const rl = createInterface({ input: process.stdin });
|
|
137
|
+
rl.on('line', handleMessage);
|
|
138
|
+
rl.on('close', () => process.exit(0));
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bullswarm",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Route work across coding-agent CLI subscriptions — paced by live quota meters, verified by content, never trusting exit codes.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"bullswarm": "./bin/bullswarm.js",
|
|
8
|
+
"bullswarm-mcp": "./mcp/server.mjs"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin/",
|
|
15
|
+
"src/",
|
|
16
|
+
"connectors/",
|
|
17
|
+
"skill/",
|
|
18
|
+
"mcp/",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"test": "node --test \"tests/*.test.js\"",
|
|
24
|
+
"prepublishOnly": "npm test"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/cowcow02/bullswarm.git"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/cowcow02/bullswarm#readme",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/cowcow02/bullswarm/issues"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"ai",
|
|
36
|
+
"agents",
|
|
37
|
+
"orchestration",
|
|
38
|
+
"claude",
|
|
39
|
+
"codex",
|
|
40
|
+
"grok",
|
|
41
|
+
"opencode",
|
|
42
|
+
"quota",
|
|
43
|
+
"routing",
|
|
44
|
+
"mcp"
|
|
45
|
+
],
|
|
46
|
+
"author": "cowcow02",
|
|
47
|
+
"license": "MIT"
|
|
48
|
+
}
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bullswarm-setup
|
|
3
|
+
description: Use when the user wants to offload work to other coding-agent subscriptions, asks about bullswarm, or wants quota-aware routing across agent CLIs. Guides the user to run the bullswarm setup wizard, which discovers installed agent CLIs, shows quota state, and configures routing.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# bullswarm setup companion
|
|
7
|
+
|
|
8
|
+
You are a guide, not the installer. The CLI owns the flow; you nudge.
|
|
9
|
+
|
|
10
|
+
## When to use
|
|
11
|
+
|
|
12
|
+
- The user asks to route/offload work to other agent CLIs (codex, grok, opencode, etc.)
|
|
13
|
+
- The user mentions quota exhaustion on one subscription while others sit idle
|
|
14
|
+
- The user asks what bullswarm is or how to set it up
|
|
15
|
+
|
|
16
|
+
## What to do
|
|
17
|
+
|
|
18
|
+
1. Check whether bullswarm is configured:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
ls ~/.bullswarm/state.json 2>/dev/null && echo configured || echo not-configured
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
2. If **not-configured**, tell the user:
|
|
25
|
+
|
|
26
|
+
> bullswarm routes work across your installed coding-agent CLIs, paced by
|
|
27
|
+
> each subscription's quota window, and verifies every delegate's output
|
|
28
|
+
> by content before trusting it. Run `bullswarm` (bare) to start the
|
|
29
|
+
> setup wizard — it discovers your installed agent CLIs, shows their
|
|
30
|
+
> quota state, and lets you pick which pools to enable. No credentials
|
|
31
|
+
> are entered anywhere; it only reads what's already on your machine.
|
|
32
|
+
|
|
33
|
+
Then offer: "Want me to run `bullswarm setup` for you now?"
|
|
34
|
+
|
|
35
|
+
3. If **configured**, surface the current state:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
bullswarm pools
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
and remind them of the daily habit:
|
|
42
|
+
|
|
43
|
+
> After every offload round, run `bullswarm health` — it re-judges saved
|
|
44
|
+
> outputs against their verdicts and catches verify-gate failures that
|
|
45
|
+
> would otherwise be invisible.
|
|
46
|
+
|
|
47
|
+
## Hard rules
|
|
48
|
+
|
|
49
|
+
- NEVER edit `~/.bullswarm/` state directly; the CLI owns it.
|
|
50
|
+
- NEVER bypass the wizard's approval gates by writing CLAUDE.md/AGENTS.md
|
|
51
|
+
blocks yourself — direct the user through `bullswarm setup` so the diff is
|
|
52
|
+
shown and approved.
|
|
53
|
+
- Delegate output is input to verify, never the answer you present.
|