@ra3orblade/swarm 0.13.0 → 0.13.2
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/dist/swarm-hook.js +9 -5
- package/dist/swarm-mcp.js +994 -1376
- package/dist/swarm.js +47 -47
- package/dist/swarmd.js +467 -418
- package/package.json +1 -1
- package/web/dashboard.css +1 -1
- package/web/dashboard.js +22 -17
- package/web/index.html +2 -3
- package/web/menus.js +7 -7
- package/web/release-notes.js +1 -1
package/dist/swarm.js
CHANGED
|
@@ -805,18 +805,18 @@ function save(s) {
|
|
|
805
805
|
}
|
|
806
806
|
function install() {
|
|
807
807
|
const s = load();
|
|
808
|
-
const
|
|
808
|
+
const hooks = s.hooks ?? {};
|
|
809
809
|
const added = [];
|
|
810
810
|
for (const ev of HOOK_EVENTS) {
|
|
811
|
-
const list =
|
|
811
|
+
const list = hooks[ev] ?? [];
|
|
812
812
|
const clean = list.map((g) => ({ ...g, hooks: g.hooks.filter((h) => !isOurs(h)) })).filter((g) => g.hooks.length);
|
|
813
813
|
clean.push({
|
|
814
814
|
hooks: [{ type: "command", command: hookCommand(ev), timeout: 5 }]
|
|
815
815
|
});
|
|
816
|
-
|
|
816
|
+
hooks[ev] = clean;
|
|
817
817
|
added.push(ev);
|
|
818
818
|
}
|
|
819
|
-
s.hooks =
|
|
819
|
+
s.hooks = hooks;
|
|
820
820
|
const stale = s.mcpServers ?? {};
|
|
821
821
|
if (stale.swarm) {
|
|
822
822
|
delete stale.swarm;
|
|
@@ -832,10 +832,10 @@ function install() {
|
|
|
832
832
|
}
|
|
833
833
|
function uninstall() {
|
|
834
834
|
const s = load();
|
|
835
|
-
const
|
|
835
|
+
const hooks = s.hooks ?? {};
|
|
836
836
|
let removed = 0;
|
|
837
|
-
for (const ev of Object.keys(
|
|
838
|
-
const before =
|
|
837
|
+
for (const ev of Object.keys(hooks)) {
|
|
838
|
+
const before = hooks[ev] ?? [];
|
|
839
839
|
const after = before.map((g) => ({
|
|
840
840
|
...g,
|
|
841
841
|
hooks: g.hooks.filter((h) => {
|
|
@@ -847,12 +847,12 @@ function uninstall() {
|
|
|
847
847
|
})
|
|
848
848
|
})).filter((g) => g.hooks.length);
|
|
849
849
|
if (after.length)
|
|
850
|
-
|
|
850
|
+
hooks[ev] = after;
|
|
851
851
|
else
|
|
852
|
-
delete
|
|
852
|
+
delete hooks[ev];
|
|
853
853
|
}
|
|
854
|
-
if (Object.keys(
|
|
855
|
-
s.hooks =
|
|
854
|
+
if (Object.keys(hooks).length)
|
|
855
|
+
s.hooks = hooks;
|
|
856
856
|
else
|
|
857
857
|
delete s.hooks;
|
|
858
858
|
const mcp = s.mcpServers ?? {};
|
|
@@ -870,8 +870,8 @@ function uninstall() {
|
|
|
870
870
|
}
|
|
871
871
|
function status() {
|
|
872
872
|
const s = load();
|
|
873
|
-
const
|
|
874
|
-
const installed = Object.values(
|
|
873
|
+
const hooks = s.hooks ?? {};
|
|
874
|
+
const installed = Object.values(hooks).some((l) => l.some((g) => g.hooks.some(isOurs)));
|
|
875
875
|
const otherAgents = [];
|
|
876
876
|
const cp = codexConfigPath();
|
|
877
877
|
if (existsSync4(cp) && CODEX_BLOCK_RE.test(readFileSync3(cp, "utf8")))
|
|
@@ -1177,15 +1177,15 @@ Open the dashboard: ${base}`);
|
|
|
1177
1177
|
line(st.mcp, "MCP server registered", "run: swarm install");
|
|
1178
1178
|
if (st.otherAgents.length)
|
|
1179
1179
|
console.log(`\u2713 MCP server also registered for ${st.otherAgents.join(", ")} (swarm_* tools in those CLIs too)`);
|
|
1180
|
-
const
|
|
1180
|
+
const forge = (bin, auth) => {
|
|
1181
1181
|
const path = Bun.which(bin);
|
|
1182
1182
|
if (!path)
|
|
1183
1183
|
return console.log(`\xB7 ${bin} not found \u2014 PRs view skips ${bin === "gh" ? "GitHub" : "GitLab"} repos`);
|
|
1184
1184
|
const ok = Bun.spawnSync([path, ...auth], { stdout: "ignore", stderr: "ignore" }).exitCode === 0;
|
|
1185
1185
|
line(ok, `${bin} authenticated (${path})`, `run: ${bin} auth login`);
|
|
1186
1186
|
};
|
|
1187
|
-
|
|
1188
|
-
|
|
1187
|
+
forge("gh", ["auth", "status", "--active", "-h", "github.com"]);
|
|
1188
|
+
forge("glab", ["auth", "status"]);
|
|
1189
1189
|
if (running) {
|
|
1190
1190
|
const t = await api("/v1/team").catch(() => null);
|
|
1191
1191
|
if (t?.configured) {
|
|
@@ -1279,7 +1279,7 @@ url: ${resolveBaseUrl()}`);
|
|
|
1279
1279
|
headers: { "content-type": "application/json" },
|
|
1280
1280
|
body: JSON.stringify({ path: resolve3(".") })
|
|
1281
1281
|
});
|
|
1282
|
-
const
|
|
1282
|
+
const post = (path, body) => authedFetch(`${new SwarmClient().baseUrl}${path}`, {
|
|
1283
1283
|
method: "POST",
|
|
1284
1284
|
headers: { "content-type": "application/json" },
|
|
1285
1285
|
body: JSON.stringify({ projectId: proj.id, ...body })
|
|
@@ -1304,7 +1304,7 @@ url: ${resolveBaseUrl()}`);
|
|
|
1304
1304
|
case "create": {
|
|
1305
1305
|
if (!target)
|
|
1306
1306
|
throw new Error("usage: swarm wt create <name> [--base ref] [--branch name]");
|
|
1307
|
-
const r = await
|
|
1307
|
+
const r = await post("/v1/worktrees", {
|
|
1308
1308
|
name: target,
|
|
1309
1309
|
baseRef: flag("--base"),
|
|
1310
1310
|
branch: flag("--branch")
|
|
@@ -1339,7 +1339,7 @@ url: ${resolveBaseUrl()}`);
|
|
|
1339
1339
|
case "open": {
|
|
1340
1340
|
if (!target)
|
|
1341
1341
|
throw new Error("usage: swarm wt open <name|path>");
|
|
1342
|
-
const r = await
|
|
1342
|
+
const r = await post("/v1/worktrees/open", { worktree: target });
|
|
1343
1343
|
if (refuse(r) || json)
|
|
1344
1344
|
break;
|
|
1345
1345
|
console.log(`opened ${r.worktree}`);
|
|
@@ -1348,7 +1348,7 @@ url: ${resolveBaseUrl()}`);
|
|
|
1348
1348
|
case "rm": {
|
|
1349
1349
|
if (!target)
|
|
1350
1350
|
throw new Error("usage: swarm wt rm <name|path> [--force]");
|
|
1351
|
-
const r = await
|
|
1351
|
+
const r = await post("/v1/worktrees/remove", {
|
|
1352
1352
|
worktree: target,
|
|
1353
1353
|
force: rest.includes("--force")
|
|
1354
1354
|
});
|
|
@@ -1385,7 +1385,7 @@ url: ${resolveBaseUrl()}`);
|
|
|
1385
1385
|
break;
|
|
1386
1386
|
}
|
|
1387
1387
|
case "gc": {
|
|
1388
|
-
const r = await
|
|
1388
|
+
const r = await post("/v1/worktrees/gc", { apply: rest.includes("--apply") });
|
|
1389
1389
|
if (json)
|
|
1390
1390
|
console.log(JSON.stringify(r));
|
|
1391
1391
|
else if (!r.candidates.length)
|
|
@@ -1487,12 +1487,12 @@ url: ${resolveBaseUrl()}`);
|
|
|
1487
1487
|
break;
|
|
1488
1488
|
}
|
|
1489
1489
|
if (sub === "clear") {
|
|
1490
|
-
const
|
|
1490
|
+
const r = await authedFetch(`${new SwarmClient().baseUrl}/v1/dispatch`, {
|
|
1491
1491
|
method: "DELETE",
|
|
1492
1492
|
headers: { "content-type": "application/json" },
|
|
1493
1493
|
body: JSON.stringify({ projectId: proj.id, task: positional[1] })
|
|
1494
1494
|
}).then((x) => x.json());
|
|
1495
|
-
console.log(json ? JSON.stringify(
|
|
1495
|
+
console.log(json ? JSON.stringify(r) : `cleared ${r.cleared}`);
|
|
1496
1496
|
break;
|
|
1497
1497
|
}
|
|
1498
1498
|
const r = await authedFetch(`${new SwarmClient().baseUrl}/v1/dispatch`, {
|
|
@@ -1645,20 +1645,20 @@ ${flag("--body") ?? d.body}`);
|
|
|
1645
1645
|
else if (!runs.length)
|
|
1646
1646
|
console.log("no live runs here");
|
|
1647
1647
|
else
|
|
1648
|
-
for (const
|
|
1649
|
-
console.log(`${
|
|
1648
|
+
for (const r of runs)
|
|
1649
|
+
console.log(`${r.id} ${r.task.padEnd(12)} pid ${String(r.pid).padEnd(7)} ${r.owner.padEnd(10)} ${r.result ? `$${r.result.costUsd.toFixed(2)} \xB7 ${r.result.turns} turns${r.result.isError ? " \xB7 error" : ""}` : "starting\u2026"}`);
|
|
1650
1650
|
break;
|
|
1651
1651
|
}
|
|
1652
1652
|
if (sub === "send" || sub === "stop") {
|
|
1653
1653
|
const target = positionals[1];
|
|
1654
1654
|
if (!target)
|
|
1655
1655
|
throw new Error(`usage: swarm run ${sub} <task|id>${sub === "send" ? ' "text"' : ""}`);
|
|
1656
|
-
const
|
|
1656
|
+
const r = sub === "send" ? await authedFetch(`${base}/v1/runs/${encodeURIComponent(target)}/send`, {
|
|
1657
1657
|
method: "POST",
|
|
1658
1658
|
headers: { "content-type": "application/json" },
|
|
1659
1659
|
body: JSON.stringify({ text: positionals.slice(2).join(" ") })
|
|
1660
1660
|
}) : await authedFetch(`${base}/v1/runs/${encodeURIComponent(target)}`, { method: "DELETE" });
|
|
1661
|
-
const j = await
|
|
1661
|
+
const j = await r.json();
|
|
1662
1662
|
if (json)
|
|
1663
1663
|
console.log(JSON.stringify(j));
|
|
1664
1664
|
else if (j.ok)
|
|
@@ -1720,8 +1720,8 @@ ${flag("--body") ?? d.body}`);
|
|
|
1720
1720
|
body: JSON.stringify({ path: resolve3(".") })
|
|
1721
1721
|
});
|
|
1722
1722
|
if (cmd === "resume") {
|
|
1723
|
-
const
|
|
1724
|
-
const j = await
|
|
1723
|
+
const r = await authedFetch(`${new SwarmClient().baseUrl}/v1/handoffs?project=${proj.id}&task=${encodeURIComponent(task)}`);
|
|
1724
|
+
const j = await r.json();
|
|
1725
1725
|
if (json)
|
|
1726
1726
|
console.log(JSON.stringify(j.handoff));
|
|
1727
1727
|
else
|
|
@@ -1805,7 +1805,7 @@ ${flag("--body") ?? d.body}`);
|
|
|
1805
1805
|
break;
|
|
1806
1806
|
}
|
|
1807
1807
|
if (sub === "run") {
|
|
1808
|
-
const [, task, ...
|
|
1808
|
+
const [, task, ...gates] = positionals;
|
|
1809
1809
|
if (!task)
|
|
1810
1810
|
throw new Error("usage: swarm gate run <task> [gate\u2026] (default: the required gates that have a cmd)");
|
|
1811
1811
|
const r = await authedFetch(`${new SwarmClient().baseUrl}/v1/gates/run`, {
|
|
@@ -1814,7 +1814,7 @@ ${flag("--body") ?? d.body}`);
|
|
|
1814
1814
|
body: JSON.stringify({
|
|
1815
1815
|
projectId: proj.id,
|
|
1816
1816
|
task,
|
|
1817
|
-
gates
|
|
1817
|
+
gates,
|
|
1818
1818
|
sessionId: process.env.CLAUDE_SESSION_ID ?? null
|
|
1819
1819
|
})
|
|
1820
1820
|
}).then((x) => x.json());
|
|
@@ -1903,22 +1903,22 @@ ${flag("--body") ?? d.body}`);
|
|
|
1903
1903
|
const w = await api(`/v1/workflows?project=${proj.id}`);
|
|
1904
1904
|
const names = Object.keys(w.defs);
|
|
1905
1905
|
console.log(names.length ? `declared: ${names.join(", ")}` : "no [[workflows]] declared in .swarm.toml");
|
|
1906
|
-
for (const
|
|
1907
|
-
console.log(`#${
|
|
1906
|
+
for (const r of w.runs.slice(0, 20))
|
|
1907
|
+
console.log(`#${r.id} ${r.task} \xB7 ${r.workflow} \xB7 ${r.state === "running" ? `${r.stepLabel} (${r.step + 1}/${r.steps.length})` : r.state}${r.detail ? ` \u2014 ${r.detail.slice(0, 80)}` : ""}`);
|
|
1908
1908
|
break;
|
|
1909
1909
|
}
|
|
1910
1910
|
if (rest[0] === "stop") {
|
|
1911
|
-
const
|
|
1912
|
-
if (!
|
|
1911
|
+
const task = rest[1];
|
|
1912
|
+
if (!task)
|
|
1913
1913
|
throw new Error("usage: swarm workflow stop <task>");
|
|
1914
|
-
const
|
|
1914
|
+
const r = await api("/v1/workflows/stop", {
|
|
1915
1915
|
method: "POST",
|
|
1916
1916
|
headers: { "content-type": "application/json" },
|
|
1917
|
-
body: JSON.stringify({ projectId: proj.id, task
|
|
1917
|
+
body: JSON.stringify({ projectId: proj.id, task })
|
|
1918
1918
|
});
|
|
1919
|
-
if (!
|
|
1920
|
-
throw new Error(
|
|
1921
|
-
console.log(`stopped the workflow on ${
|
|
1919
|
+
if (!r.ok)
|
|
1920
|
+
throw new Error(r.error ?? "stop failed");
|
|
1921
|
+
console.log(`stopped the workflow on ${task}`);
|
|
1922
1922
|
break;
|
|
1923
1923
|
}
|
|
1924
1924
|
const [name, task] = rest;
|
|
@@ -2318,13 +2318,13 @@ would have fired (newest last):`);
|
|
|
2318
2318
|
return x != null && Number.isFinite(x) ? x : undefined;
|
|
2319
2319
|
};
|
|
2320
2320
|
if (sub === "ls" || !sub) {
|
|
2321
|
-
const
|
|
2321
|
+
const list = await api("/v1/resources");
|
|
2322
2322
|
if (json)
|
|
2323
|
-
console.log(JSON.stringify(
|
|
2324
|
-
else if (!
|
|
2323
|
+
console.log(JSON.stringify(list));
|
|
2324
|
+
else if (!list.length)
|
|
2325
2325
|
console.log("no resources held");
|
|
2326
2326
|
else
|
|
2327
|
-
for (const r of
|
|
2327
|
+
for (const r of list)
|
|
2328
2328
|
console.log(`${r.kind.padEnd(8)} ${r.name.padEnd(16)} ${r.owner.padEnd(12)} ${r.port != null ? `:${r.port}` : r.pid != null ? `pid ${r.pid}` : ""}`);
|
|
2329
2329
|
break;
|
|
2330
2330
|
}
|
|
@@ -2424,13 +2424,13 @@ would have fired (newest last):`);
|
|
|
2424
2424
|
case "demo": {
|
|
2425
2425
|
const home = join5(swarmHome(), "demo");
|
|
2426
2426
|
const port = "7799";
|
|
2427
|
-
const [
|
|
2428
|
-
if (!
|
|
2427
|
+
const [cmd, ...args] = daemonCommand();
|
|
2428
|
+
if (!cmd)
|
|
2429
2429
|
throw new Error("could not resolve the daemon command");
|
|
2430
2430
|
const env = { ...process.env, SWARM_HOME: home, SWARM_PORT: port, SWARM_DEMO: "1" };
|
|
2431
2431
|
const up = await authedFetch(`http://127.0.0.1:${port}/v1/health`).then((r) => r.ok).catch(() => false);
|
|
2432
2432
|
if (!up) {
|
|
2433
|
-
Bun.spawn([
|
|
2433
|
+
Bun.spawn([cmd, ...args], {
|
|
2434
2434
|
stdin: "ignore",
|
|
2435
2435
|
stdout: "ignore",
|
|
2436
2436
|
stderr: "ignore",
|