crewswarm 0.9.4 → 1.0.0
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/.env.example +8 -1
- package/README.md +58 -9
- package/apps/dashboard/README.md +49 -0
- package/apps/dashboard/dist/assets/{index-D-sRshvg.css → index-C5-vlIwl.css} +1 -1
- package/apps/dashboard/dist/assets/index-CSooN9fi.js +2 -0
- package/apps/dashboard/dist/assets/index-CSooN9fi.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-spending-tab-DcXD5TQY.js +1 -0
- package/apps/dashboard/dist/assets/tab-spending-tab-DcXD5TQY.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-testing-tab-Ea5K-rsb.js +1 -0
- package/apps/dashboard/dist/index.html +85 -7
- package/apps/dashboard/dist/index.html.br +0 -0
- package/contrib/openclaw-plugin/index.ts +20 -11
- package/install.sh +2 -2
- package/lib/autoharness/index.mjs +151 -1
- package/lib/chat/history.mjs +1 -1
- package/lib/contacts/identity-linker.mjs +24 -3
- package/lib/contacts/index.mjs +2 -1
- package/lib/crew-lead/chat-handler.mjs +56 -33
- package/lib/crew-lead/llm-caller.mjs +71 -14
- package/lib/crew-lead/prompts.mjs +4 -2
- package/lib/crew-lead/wave-dispatcher.mjs +53 -3
- package/lib/crew-lead/worktree.mjs +258 -0
- package/lib/crew-lead/ws-router.mjs +43 -0
- package/lib/engines/rt-envelope.mjs +4 -1
- package/lib/memory/relevance-scorer.mjs +199 -0
- package/lib/memory/shared-adapter.mjs +85 -19
- package/package.json +10 -3
- package/scripts/dashboard.mjs +398 -28
- package/scripts/health-check.mjs +70 -28
- package/scripts/install-docker.sh +1 -1
- package/scripts/restart-all-from-repo.sh +25 -21
- package/scripts/start.mjs +81 -26
- package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js.br +0 -0
- package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js.br +0 -0
- package/apps/dashboard/dist/assets/components-BS9fQjE_.js.br +0 -0
- package/apps/dashboard/dist/assets/core-utils-CmOkXgzi.js.br +0 -0
- package/apps/dashboard/dist/assets/index-BeVllEj_.js +0 -2
- package/apps/dashboard/dist/assets/index-BeVllEj_.js.br +0 -0
- package/apps/dashboard/dist/assets/index-D-sRshvg.css.br +0 -0
- package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js.br +0 -0
- package/apps/dashboard/dist/assets/setup-wizard-CA0Or47w.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-agents-tab-BgpIsjkw.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-benchmarks-tab-BHjKCPm3.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-comms-tab-kguqTIzD.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-contacts-tab-DiOyMYth.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-engines-tab-BsdZVvU0.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-memory-tab-Cu6u13EQ.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-DiAPTJXu.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-projects-tab-SFH4E--a.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-prompts-tab-DVkUNaJd.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-services-tab-DU_LH3uG.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-spending-tab-DEccQHnt.js +0 -1
- package/apps/dashboard/dist/assets/tab-spending-tab-DEccQHnt.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BNrd88-r.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-tab-B1AcjL1W.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js +0 -1
- package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-usage-tab-BIOOnB-Y.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-workflows-tab-B-soSy1k.js.br +0 -0
- package/apps/dashboard/dist/index.html.gz +0 -0
package/scripts/health-check.mjs
CHANGED
|
@@ -16,6 +16,8 @@ import fs from "node:fs";
|
|
|
16
16
|
import path from "node:path";
|
|
17
17
|
import os from "node:os";
|
|
18
18
|
import { execSync } from "node:child_process";
|
|
19
|
+
import http from "node:http";
|
|
20
|
+
import https from "node:https";
|
|
19
21
|
|
|
20
22
|
const JSON_MODE = process.argv.includes("--json");
|
|
21
23
|
const QUIET_MODE = process.argv.includes("--quiet");
|
|
@@ -51,9 +53,31 @@ function authHeaders() {
|
|
|
51
53
|
return { "content-type": "application/json", ...(t ? { authorization: `Bearer ${t}` } : {}) };
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
function request(url, opts = {}) {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const parsed = new URL(url);
|
|
59
|
+
const transport = parsed.protocol === "https:" ? https : http;
|
|
60
|
+
const req = transport.request(parsed, {
|
|
61
|
+
method: opts.method || "GET",
|
|
62
|
+
headers: opts.headers || {},
|
|
63
|
+
}, (res) => {
|
|
64
|
+
let body = "";
|
|
65
|
+
res.setEncoding("utf8");
|
|
66
|
+
res.on("data", chunk => { body += chunk; });
|
|
67
|
+
res.on("end", () => resolve({ status: res.statusCode || 0, body, headers: res.headers }));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
req.on("error", reject);
|
|
71
|
+
req.setTimeout(opts.timeout || 10000, () => req.destroy(new Error("timeout")));
|
|
72
|
+
|
|
73
|
+
if (opts.body) req.write(opts.body);
|
|
74
|
+
req.end();
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
54
78
|
async function ping(url, label, opts = {}) {
|
|
55
79
|
try {
|
|
56
|
-
const res = await
|
|
80
|
+
const res = await request(url, { timeout: opts.timeout || 10000, headers: authHeaders() });
|
|
57
81
|
return { ok: res.ok || res.status < 500, status: res.status };
|
|
58
82
|
} catch (e) {
|
|
59
83
|
return { ok: false, error: e.message };
|
|
@@ -84,7 +108,7 @@ async function run() {
|
|
|
84
108
|
check("~/.crewswarm/crewswarm.json", hasSwarm ? "pass" : "fail", hasSwarm ? "" : "run: bash install.sh");
|
|
85
109
|
|
|
86
110
|
const token = getToken();
|
|
87
|
-
check("Auth token", token ? "pass" : "
|
|
111
|
+
check("Auth token", token ? "pass" : "warn", token ? `${token.slice(0,8)}…` : "no rt.authToken (optional for local dev)");
|
|
88
112
|
|
|
89
113
|
// ── 2. API keys ──────────────────────────────────────────────────────────────
|
|
90
114
|
section("API Keys");
|
|
@@ -127,19 +151,32 @@ async function run() {
|
|
|
127
151
|
if (!NO_SERVICES) {
|
|
128
152
|
section("Agents");
|
|
129
153
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
154
|
+
// Try with auth first, fall back to no-auth for local dev
|
|
155
|
+
let res = await request(`${CREW_LEAD}/api/agents`, { headers: authHeaders(), timeout: 5000 });
|
|
156
|
+
if (res.status === 401) {
|
|
157
|
+
// No token or wrong token — try the dashboard proxy which may not require auth
|
|
158
|
+
try {
|
|
159
|
+
res = await request(`${DASHBOARD}/api/agents`, { timeout: 5000 });
|
|
160
|
+
} catch { /* dashboard proxy also failed, use original 401 response */ }
|
|
161
|
+
}
|
|
162
|
+
const d = JSON.parse(res.body || "{}");
|
|
163
|
+
// Dashboard returns array directly, crew-lead returns { agents: [...] }
|
|
164
|
+
const agents = Array.isArray(d) ? d : (d.agents || []);
|
|
165
|
+
if (agents.length === 0 && d.error) {
|
|
166
|
+
// Auth required but no valid token — report agent count from bridge process list
|
|
167
|
+
const bridgeCount = (() => { try { return execSync("pgrep -f 'gateway-bridge.mjs' | wc -l", { encoding: "utf8", timeout: 2000 }).trim(); } catch { return "0"; } })();
|
|
168
|
+
check(`Agents`, bridgeCount > 0 ? "pass" : "warn", `${bridgeCount} bridge processes running (auth required for detailed status)`);
|
|
169
|
+
} else {
|
|
170
|
+
const online = agents.filter(a => a.online || a.alive || a.liveness === "online" || a.liveness === "alive");
|
|
171
|
+
const coreAgents = ["crew-coder","crew-qa","crew-pm","crew-main","crew-fixer"];
|
|
172
|
+
check(`Agents online (${online.length}/${agents.length})`,
|
|
173
|
+
online.length > 0 ? "pass" : "warn",
|
|
174
|
+
online.length === 0 ? "bridges not started — run: npm run start-crew" : online.map(a => a.id?.replace("crew-","")).join(", ").slice(0,80));
|
|
175
|
+
for (const core of coreAgents) {
|
|
176
|
+
const a = agents.find(x => x.id === core);
|
|
177
|
+
const isOnline = a?.online || a?.alive || a?.liveness === "online" || a?.liveness === "alive";
|
|
178
|
+
check(` ${core}`, isOnline ? "pass" : "warn", isOnline ? "" : "bridge not running");
|
|
179
|
+
}
|
|
143
180
|
}
|
|
144
181
|
} catch (e) {
|
|
145
182
|
check("Agents", "fail", `could not reach crew-lead: ${e.message}`);
|
|
@@ -150,7 +187,7 @@ async function run() {
|
|
|
150
187
|
section("CLI Tools");
|
|
151
188
|
const [cursorCli, claudeCli, opencodeCli, nodeCli] = await Promise.all([
|
|
152
189
|
Promise.resolve(cliCheck("cursor --version 2>/dev/null || cursor-cli --version 2>/dev/null", "cursor")),
|
|
153
|
-
Promise.resolve(cliCheck("
|
|
190
|
+
Promise.resolve(cliCheck("which claude 2>/dev/null && claude --version 2>/dev/null || echo 'not found'", "claude")),
|
|
154
191
|
Promise.resolve(cliCheck("opencode --version 2>/dev/null || echo 'not found'", "opencode")),
|
|
155
192
|
Promise.resolve(cliCheck("node --version", "node")),
|
|
156
193
|
]);
|
|
@@ -168,23 +205,23 @@ async function run() {
|
|
|
168
205
|
if (!NO_SERVICES && mcpServer.ok) {
|
|
169
206
|
section("MCP Protocol");
|
|
170
207
|
try {
|
|
171
|
-
const initRes = await
|
|
208
|
+
const initRes = await request(`${MCP_URL}/mcp`, {
|
|
172
209
|
method: "POST",
|
|
173
210
|
headers: { "content-type": "application/json" },
|
|
174
211
|
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2024-11-05", clientInfo: { name: "health-check", version: "1.0" } } }),
|
|
175
|
-
|
|
212
|
+
timeout: 4000,
|
|
176
213
|
});
|
|
177
|
-
const initData =
|
|
214
|
+
const initData = JSON.parse(initRes.body || "{}");
|
|
178
215
|
check("MCP initialize", initData?.result?.serverInfo ? "pass" : "fail",
|
|
179
216
|
initData?.result?.serverInfo?.name || JSON.stringify(initData).slice(0,60));
|
|
180
217
|
|
|
181
|
-
const toolsRes = await
|
|
218
|
+
const toolsRes = await request(`${MCP_URL}/mcp`, {
|
|
182
219
|
method: "POST",
|
|
183
220
|
headers: { "content-type": "application/json" },
|
|
184
221
|
body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} }),
|
|
185
|
-
|
|
222
|
+
timeout: 4000,
|
|
186
223
|
});
|
|
187
|
-
const toolsData =
|
|
224
|
+
const toolsData = JSON.parse(toolsRes.body || "{}");
|
|
188
225
|
const toolCount = toolsData?.result?.tools?.length || 0;
|
|
189
226
|
check(`MCP tools/list (${toolCount} tools)`, toolCount >= 5 ? "pass" : "warn",
|
|
190
227
|
toolsData?.result?.tools?.map(t => t.name).join(", ").slice(0, 80));
|
|
@@ -200,17 +237,22 @@ async function run() {
|
|
|
200
237
|
} else {
|
|
201
238
|
try {
|
|
202
239
|
const start = Date.now();
|
|
203
|
-
const res = await
|
|
240
|
+
const res = await request(`${CREW_LEAD}/chat`, {
|
|
204
241
|
method: "POST",
|
|
205
242
|
headers: authHeaders(),
|
|
206
243
|
body: JSON.stringify({ message: "say: HEALTH_OK", sessionId: "health-check" }),
|
|
207
|
-
|
|
244
|
+
timeout: 15000,
|
|
208
245
|
});
|
|
209
|
-
const d =
|
|
246
|
+
const d = JSON.parse(res.body || "{}");
|
|
210
247
|
const elapsed = Date.now() - start;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
248
|
+
if (res.status === 401) {
|
|
249
|
+
// Auth required — crew-lead is up but we can't chat without token
|
|
250
|
+
check("crew-lead responds", "pass", `up (auth required for chat test)`);
|
|
251
|
+
} else {
|
|
252
|
+
const reply = d.reply || d.message || "";
|
|
253
|
+
check("crew-lead responds", reply.length > 0 ? "pass" : "warn",
|
|
254
|
+
`${Math.round(elapsed/100)/10}s — "${reply.slice(0,60) || "(empty reply)"}"`);
|
|
255
|
+
}
|
|
214
256
|
} catch (e) {
|
|
215
257
|
check("crew-lead chat", "fail", e.message);
|
|
216
258
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# crewswarm Docker Installer — one-line setup for cloud VMs and dedicated servers
|
|
3
|
-
# Usage: curl -fsSL https://raw.githubusercontent.com/
|
|
3
|
+
# Usage: curl -fsSL https://raw.githubusercontent.com/crewswarm/crewswarm/main/scripts/install-docker.sh | bash
|
|
4
4
|
|
|
5
5
|
set -e
|
|
6
6
|
|
|
@@ -21,8 +21,9 @@ YELLOW='\033[0;33m'
|
|
|
21
21
|
BOLD='\033[1m'
|
|
22
22
|
RESET='\033[0m'
|
|
23
23
|
|
|
24
|
-
# ── Service status tracking
|
|
25
|
-
|
|
24
|
+
# ── Service status tracking (bash 3.2 compatible — no associative arrays) ────
|
|
25
|
+
svc_set() { eval "SVC_$(echo "$1" | tr '-' '_')=$2"; }
|
|
26
|
+
svc_get() { eval "echo \${SVC_$(echo "$1" | tr '-' '_'):-unknown}"; }
|
|
26
27
|
|
|
27
28
|
# ── Graceful kill: SIGTERM first, SIGKILL after 5s ────────────────────────────
|
|
28
29
|
graceful_kill_pattern() {
|
|
@@ -71,14 +72,14 @@ wait_for_health() {
|
|
|
71
72
|
while [ $attempt -lt $max_attempts ]; do
|
|
72
73
|
if curl -s --max-time 2 "$url" > /dev/null 2>&1; then
|
|
73
74
|
echo -e " ${GREEN}✓${RESET} $name is up"
|
|
74
|
-
|
|
75
|
+
svc_set "$name" "up"
|
|
75
76
|
return 0
|
|
76
77
|
fi
|
|
77
78
|
attempt=$((attempt + 1))
|
|
78
79
|
sleep 1
|
|
79
80
|
done
|
|
80
81
|
echo -e " ${RED}✗${RESET} $name failed to start (tried ${max_attempts}s)"
|
|
81
|
-
|
|
82
|
+
svc_set "$name" "down"
|
|
82
83
|
return 1
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -112,6 +113,8 @@ graceful_kill_pattern "telegram-bridge.mjs"
|
|
|
112
113
|
graceful_kill_pattern "whatsapp-bridge.mjs"
|
|
113
114
|
graceful_kill_pattern "opencode serve"
|
|
114
115
|
graceful_kill_pattern "pm-loop.mjs"
|
|
116
|
+
graceful_kill_pattern "node --test" # orphaned test runners from dashboard
|
|
117
|
+
graceful_kill_pattern "npx playwright" # orphaned playwright runners
|
|
115
118
|
graceful_kill_pattern "apps/vibe/server.mjs"
|
|
116
119
|
graceful_kill_pattern "watch-server.mjs"
|
|
117
120
|
graceful_kill_pattern "vite.*vibe"
|
|
@@ -151,11 +154,11 @@ OPENCODE_BIN="$(command -v opencode 2>/dev/null)" || OPENCODE_BIN="/usr/local/bi
|
|
|
151
154
|
if [[ -x "$OPENCODE_BIN" ]]; then
|
|
152
155
|
nohup "$OPENCODE_BIN" serve --port 4096 --hostname 127.0.0.1 >> /tmp/opencode.log 2>&1 &
|
|
153
156
|
# Non-critical — don't block on health
|
|
154
|
-
|
|
157
|
+
svc_set "opencode" "up"
|
|
155
158
|
echo " Started (PID $!)"
|
|
156
159
|
else
|
|
157
160
|
echo " (opencode not found; skip)"
|
|
158
|
-
|
|
161
|
+
svc_set "opencode" "skip"
|
|
159
162
|
fi
|
|
160
163
|
|
|
161
164
|
# ── 2. RT daemon (port 18889) ────────────────────────────────────────────────
|
|
@@ -181,7 +184,7 @@ fi
|
|
|
181
184
|
echo ""
|
|
182
185
|
echo "Starting gateway bridges (crew-main, crew-pm, crew-coder, etc.)..."
|
|
183
186
|
"$NODE" scripts/start-crew.mjs --force
|
|
184
|
-
|
|
187
|
+
svc_set "agents" "up"
|
|
185
188
|
sleep 1
|
|
186
189
|
|
|
187
190
|
# ── 4. crew-lead (port 5010) — CRITICAL ─────────────────────────────────────
|
|
@@ -206,27 +209,27 @@ if [[ "$START_DASH" -eq 1 ]]; then
|
|
|
206
209
|
fi
|
|
207
210
|
wait_for_health "http://127.0.0.1:4319/" "dashboard" 30 || true
|
|
208
211
|
else
|
|
209
|
-
|
|
212
|
+
svc_set "dashboard" "skip"
|
|
210
213
|
fi
|
|
211
214
|
|
|
212
215
|
# ── 6. Messaging bridges (Telegram + WhatsApp) ──────────────────────────────
|
|
213
216
|
if [[ "$START_BRIDGES" -eq 1 ]]; then
|
|
214
217
|
echo ""
|
|
215
218
|
# Verify RT bus is still up before starting bridges
|
|
216
|
-
if [[ "$
|
|
219
|
+
if [[ "$(svc_get rt-bus)" == "up" ]]; then
|
|
217
220
|
echo "Starting Telegram bridge..."
|
|
218
221
|
nohup "$NODE" "$REPO_DIR/telegram-bridge.mjs" >> /tmp/telegram-bridge.log 2>&1 &
|
|
219
|
-
|
|
222
|
+
svc_set "telegram" "up"
|
|
220
223
|
echo " PID: $!"
|
|
221
224
|
|
|
222
225
|
echo "Starting WhatsApp bridge..."
|
|
223
226
|
nohup "$NODE" "$REPO_DIR/whatsapp-bridge.mjs" >> /tmp/whatsapp-bridge.log 2>&1 &
|
|
224
|
-
|
|
227
|
+
svc_set "whatsapp" "up"
|
|
225
228
|
echo " PID: $!"
|
|
226
229
|
else
|
|
227
230
|
echo -e " ${YELLOW}Skipping messaging bridges — RT bus is not up${RESET}"
|
|
228
|
-
|
|
229
|
-
|
|
231
|
+
svc_set "telegram" "skip"
|
|
232
|
+
svc_set "whatsapp" "skip"
|
|
230
233
|
fi
|
|
231
234
|
fi
|
|
232
235
|
|
|
@@ -238,7 +241,7 @@ if ! lsof -ti :5020 >/dev/null 2>&1; then
|
|
|
238
241
|
wait_for_health "http://127.0.0.1:5020/health" "mcp-server" 15 || true
|
|
239
242
|
else
|
|
240
243
|
echo " (already running on :5020)"
|
|
241
|
-
|
|
244
|
+
svc_set "mcp-server" "up"
|
|
242
245
|
fi
|
|
243
246
|
|
|
244
247
|
# ── 8. Vibe + file watcher (ports 3333, 3334) ───────────────────────────────
|
|
@@ -257,18 +260,18 @@ if [[ "$START_STUDIO" -eq 1 ]]; then
|
|
|
257
260
|
for i in $(seq 1 10); do
|
|
258
261
|
if lsof -ti :3334 >/dev/null 2>&1; then
|
|
259
262
|
echo -e " ${GREEN}✓${RESET} file watcher is up"
|
|
260
|
-
|
|
263
|
+
svc_set "file-watcher" "up"
|
|
261
264
|
break
|
|
262
265
|
fi
|
|
263
266
|
if [[ "$i" -eq 10 ]]; then
|
|
264
267
|
echo -e " ${YELLOW}!${RESET} file watcher not detected on :3334"
|
|
265
|
-
|
|
268
|
+
svc_set "file-watcher" "down"
|
|
266
269
|
fi
|
|
267
270
|
sleep 1
|
|
268
271
|
done
|
|
269
272
|
else
|
|
270
|
-
|
|
271
|
-
|
|
273
|
+
svc_set "vibe-studio" "skip"
|
|
274
|
+
svc_set "file-watcher" "skip"
|
|
272
275
|
fi
|
|
273
276
|
|
|
274
277
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
@@ -280,7 +283,7 @@ echo -e "${BOLD}━━━ Service Summary ━━━${RESET}"
|
|
|
280
283
|
print_status() {
|
|
281
284
|
local name="$1"
|
|
282
285
|
local port="$2"
|
|
283
|
-
local status="$
|
|
286
|
+
local status="$(svc_get "$name")"
|
|
284
287
|
case "$status" in
|
|
285
288
|
up) echo -e " ${GREEN}✓${RESET} $name :$port" ;;
|
|
286
289
|
down) echo -e " ${RED}✗${RESET} $name :$port" ;;
|
|
@@ -309,12 +312,13 @@ echo "Logs: /tmp/opencode.log /tmp/opencrew-rt-daemon.log /tmp/crew-lead.log /tm
|
|
|
309
312
|
if [[ -f "$REPO_DIR/scripts/health-check.mjs" ]]; then
|
|
310
313
|
echo ""
|
|
311
314
|
echo -e "${BOLD}Running health check...${RESET}"
|
|
315
|
+
sleep 2 # let services fully warm up
|
|
312
316
|
"$NODE" "$REPO_DIR/scripts/health-check.mjs" --quiet 2>/dev/null || true
|
|
313
317
|
fi
|
|
314
318
|
|
|
315
319
|
# ── Exit code: 0 if critical services are up, 1 otherwise ────────────────────
|
|
316
|
-
CREW_LEAD_OK="$
|
|
317
|
-
DASHBOARD_OK="$
|
|
320
|
+
CREW_LEAD_OK="$(svc_get crew-lead)"
|
|
321
|
+
DASHBOARD_OK="$(svc_get dashboard)"
|
|
318
322
|
|
|
319
323
|
if [[ "$CREW_LEAD_OK" == "up" ]] && { [[ "$DASHBOARD_OK" == "up" ]] || [[ "$START_DASH" -eq 0 ]]; }; then
|
|
320
324
|
echo ""
|
package/scripts/start.mjs
CHANGED
|
@@ -2,24 +2,29 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* scripts/start.mjs — CrewSwarm first-run entry point
|
|
4
4
|
*
|
|
5
|
-
* This is the script behind `npm start`. It validates the
|
|
6
|
-
* handing off to the real stack so that a brand-new user
|
|
7
|
-
* and types `npm start` gets clear, actionable guidance
|
|
8
|
-
* trace.
|
|
5
|
+
* This is the script behind `npm start` / `npx crewswarm`. It validates the
|
|
6
|
+
* environment before handing off to the real stack so that a brand-new user
|
|
7
|
+
* who clones the repo and types `npm start` gets clear, actionable guidance
|
|
8
|
+
* rather than a stack trace.
|
|
9
9
|
*
|
|
10
10
|
* Checks performed (in order):
|
|
11
11
|
* 1. Node.js version >= 20
|
|
12
12
|
* 2. ~/.crewswarm/crewswarm.json exists (created by install.sh)
|
|
13
|
-
* 3. ~/.crewswarm/
|
|
13
|
+
* 3. ~/.crewswarm/config.json exists (created by install.sh)
|
|
14
14
|
* 4. At least one provider with an apiKey is configured
|
|
15
15
|
*
|
|
16
|
-
* On success
|
|
16
|
+
* On success:
|
|
17
|
+
* 1. Spawns start-crew.mjs in the background (RT bus + crew-lead + bridges)
|
|
18
|
+
* 2. Waits 3 seconds for services to come up
|
|
19
|
+
* 3. Starts dashboard in the foreground (the process the user sees)
|
|
20
|
+
*
|
|
21
|
+
* NOTE: `npm run dashboard` still starts only the dashboard (unchanged).
|
|
17
22
|
*/
|
|
18
23
|
|
|
19
24
|
import fs from "node:fs";
|
|
20
25
|
import path from "node:path";
|
|
21
26
|
import os from "node:os";
|
|
22
|
-
import { spawnSync } from "node:child_process";
|
|
27
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
23
28
|
import { fileURLToPath } from "node:url";
|
|
24
29
|
|
|
25
30
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -63,43 +68,53 @@ success(`Node.js v${process.versions.node}`);
|
|
|
63
68
|
// ── 2. Config directory ───────────────────────────────────────────────────────
|
|
64
69
|
const CREWSWARM_DIR = path.join(os.homedir(), ".crewswarm");
|
|
65
70
|
const SWARM_CFG = path.join(CREWSWARM_DIR, "crewswarm.json");
|
|
66
|
-
const SYS_CFG = path.join(CREWSWARM_DIR, "
|
|
71
|
+
const SYS_CFG = path.join(CREWSWARM_DIR, "config.json");
|
|
67
72
|
const INSTALL_SH = path.join(ROOT, "install.sh");
|
|
68
73
|
|
|
69
74
|
function tryReadJSON(p) {
|
|
70
75
|
try { return JSON.parse(fs.readFileSync(p, "utf8")); } catch { return null; }
|
|
71
76
|
}
|
|
72
77
|
|
|
78
|
+
// Bootstrap config directory + minimal config if missing — the dashboard's
|
|
79
|
+
// setup wizard will guide the user through API key entry on first visit.
|
|
80
|
+
let firstRun = false;
|
|
73
81
|
if (!fs.existsSync(CREWSWARM_DIR)) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
`Or, for a one-liner from the web:\n\n` +
|
|
78
|
-
` bash <(curl -fsSL https://raw.githubusercontent.com/crewswarm/crewswarm/main/install.sh)`
|
|
79
|
-
);
|
|
82
|
+
fs.mkdirSync(CREWSWARM_DIR, { recursive: true });
|
|
83
|
+
info(`Created config directory: ${CREWSWARM_DIR}`);
|
|
84
|
+
firstRun = true;
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
// ── 3. crewswarm.json ─────────────────────────────────────────────────────────
|
|
83
88
|
if (!fs.existsSync(SWARM_CFG)) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
const defaultConfig = {
|
|
90
|
+
agents: [
|
|
91
|
+
{ id: "crew-lead", model: "groq/llama-3.3-70b-versatile" },
|
|
92
|
+
{ id: "crew-main", model: "groq/llama-3.3-70b-versatile" },
|
|
93
|
+
{ id: "crew-coder", model: "groq/llama-3.3-70b-versatile" },
|
|
94
|
+
{ id: "crew-qa", model: "groq/llama-3.3-70b-versatile" },
|
|
95
|
+
{ id: "crew-fixer", model: "groq/llama-3.3-70b-versatile" },
|
|
96
|
+
{ id: "crew-pm", model: "groq/llama-3.3-70b-versatile" }
|
|
97
|
+
],
|
|
98
|
+
providers: {}
|
|
99
|
+
};
|
|
100
|
+
fs.writeFileSync(SWARM_CFG, JSON.stringify(defaultConfig, null, 2));
|
|
101
|
+
info(`Created default ${SWARM_CFG} — add API keys in the dashboard setup wizard`);
|
|
102
|
+
firstRun = true;
|
|
88
103
|
}
|
|
89
|
-
|
|
104
|
+
let swarm = tryReadJSON(SWARM_CFG);
|
|
90
105
|
if (!swarm) {
|
|
91
106
|
fatal(
|
|
92
107
|
`Cannot parse ${SWARM_CFG} — file may be corrupt.`,
|
|
93
108
|
`Check it is valid JSON, or re-run:\n\n bash ${INSTALL_SH}`
|
|
94
109
|
);
|
|
95
110
|
}
|
|
96
|
-
success(`crewswarm.json found`);
|
|
111
|
+
success(`crewswarm.json found${firstRun ? " (first run — setup wizard will launch)" : ""}`);
|
|
97
112
|
|
|
98
113
|
// ── 4. config.json ───────────────────────────────────────────────────────────
|
|
99
114
|
if (!fs.existsSync(SYS_CFG)) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
115
|
+
const defaultSysCfg = { rt: { authToken: `local-${Date.now().toString(36)}` } };
|
|
116
|
+
fs.writeFileSync(SYS_CFG, JSON.stringify(defaultSysCfg, null, 2));
|
|
117
|
+
info(`Created default ${SYS_CFG}`);
|
|
103
118
|
} else {
|
|
104
119
|
const sys = tryReadJSON(SYS_CFG);
|
|
105
120
|
if (!sys) {
|
|
@@ -109,6 +124,16 @@ if (!fs.existsSync(SYS_CFG)) {
|
|
|
109
124
|
}
|
|
110
125
|
}
|
|
111
126
|
|
|
127
|
+
// ── Bootstrap supporting files if missing ────────────────────────────────────
|
|
128
|
+
const CMD_ALLOWLIST = path.join(CREWSWARM_DIR, "cmd-allowlist.json");
|
|
129
|
+
const AGENT_PROMPTS = path.join(CREWSWARM_DIR, "agent-prompts.json");
|
|
130
|
+
if (!fs.existsSync(CMD_ALLOWLIST)) {
|
|
131
|
+
fs.writeFileSync(CMD_ALLOWLIST, '["npm *","node *","npx *","git *"]');
|
|
132
|
+
}
|
|
133
|
+
if (!fs.existsSync(AGENT_PROMPTS)) {
|
|
134
|
+
fs.writeFileSync(AGENT_PROMPTS, "{}");
|
|
135
|
+
}
|
|
136
|
+
|
|
112
137
|
// ── 5. Provider check ────────────────────────────────────────────────────────
|
|
113
138
|
const providers = swarm.providers || {};
|
|
114
139
|
const configured = Object.entries(providers).filter(([, v]) => v?.apiKey && String(v.apiKey).trim().length > 0 && !String(v.apiKey).startsWith("your-"));
|
|
@@ -130,14 +155,44 @@ if (agents.length === 0) {
|
|
|
130
155
|
}
|
|
131
156
|
info(`${agents.length} agent(s) defined`);
|
|
132
157
|
|
|
133
|
-
// ── 7.
|
|
158
|
+
// ── 7. Start full stack ───────────────────────────────────────────────────────
|
|
159
|
+
console.log("");
|
|
160
|
+
divider();
|
|
161
|
+
info("All checks passed — starting full CrewSwarm stack");
|
|
162
|
+
divider();
|
|
163
|
+
console.log("");
|
|
164
|
+
|
|
165
|
+
// Step 1: Spawn start-crew.mjs in the background (RT bus + crew-lead + bridges)
|
|
166
|
+
info("Launching RT bus, crew-lead, and gateway bridges…");
|
|
167
|
+
const crewScript = path.join(ROOT, "scripts", "start-crew.mjs");
|
|
168
|
+
const crewProc = spawn(process.execPath, [crewScript], {
|
|
169
|
+
cwd: ROOT,
|
|
170
|
+
stdio: "inherit",
|
|
171
|
+
detached: false,
|
|
172
|
+
env: process.env,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Wait for start-crew.mjs to finish its synchronous setup (it exits after spawning daemons)
|
|
176
|
+
await new Promise((resolve) => {
|
|
177
|
+
crewProc.on("exit", resolve);
|
|
178
|
+
crewProc.on("error", (err) => {
|
|
179
|
+
warn(`start-crew.mjs exited with error: ${err.message}`);
|
|
180
|
+
resolve();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Step 2: Give the background daemons a moment to bind their ports
|
|
185
|
+
info("Waiting 3 s for services to come up…");
|
|
186
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
187
|
+
|
|
188
|
+
// Step 3: Start dashboard in the foreground (what the user sees)
|
|
134
189
|
console.log("");
|
|
135
190
|
divider();
|
|
136
|
-
|
|
191
|
+
console.log(bold(" All services started. Dashboard → http://127.0.0.1:4319"));
|
|
137
192
|
divider();
|
|
138
193
|
console.log("");
|
|
139
194
|
|
|
140
|
-
const result = spawnSync(
|
|
195
|
+
const result = spawnSync(process.execPath, [path.join(ROOT, "scripts", "dashboard.mjs")], {
|
|
141
196
|
cwd: ROOT,
|
|
142
197
|
stdio: "inherit",
|
|
143
198
|
env: process.env,
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|