buildwithnexus 0.7.2 → 0.7.3
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/bin.js +234 -194
- package/dist/deep-agents-bin.js +15 -9
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -428,6 +428,112 @@ var init_platform = __esm({
|
|
|
428
428
|
}
|
|
429
429
|
});
|
|
430
430
|
|
|
431
|
+
// src/core/utils.ts
|
|
432
|
+
function backoffMs(attempt) {
|
|
433
|
+
return Math.min(3e3 * Math.pow(2, attempt), 3e4);
|
|
434
|
+
}
|
|
435
|
+
var init_utils = __esm({
|
|
436
|
+
"src/core/utils.ts"() {
|
|
437
|
+
"use strict";
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// src/core/health.ts
|
|
442
|
+
var health_exports = {};
|
|
443
|
+
__export(health_exports, {
|
|
444
|
+
checkHealth: () => checkHealth,
|
|
445
|
+
waitForServer: () => waitForServer
|
|
446
|
+
});
|
|
447
|
+
async function checkHealth(vmRunning) {
|
|
448
|
+
const status = {
|
|
449
|
+
vmRunning,
|
|
450
|
+
sshReady: false,
|
|
451
|
+
dockerReady: false,
|
|
452
|
+
serverHealthy: false,
|
|
453
|
+
tunnelUrl: null,
|
|
454
|
+
dockerVersion: null,
|
|
455
|
+
serverVersion: null,
|
|
456
|
+
diskUsagePercent: null,
|
|
457
|
+
uptimeSeconds: null,
|
|
458
|
+
lastChecked: (/* @__PURE__ */ new Date()).toISOString()
|
|
459
|
+
};
|
|
460
|
+
if (!vmRunning) return status;
|
|
461
|
+
status.sshReady = true;
|
|
462
|
+
try {
|
|
463
|
+
const { stdout, code } = await dockerExec("docker version --format '{{.Server.Version}}'");
|
|
464
|
+
status.dockerReady = code === 0 && stdout.trim().length > 0;
|
|
465
|
+
if (status.dockerReady) status.dockerVersion = stdout.trim();
|
|
466
|
+
} catch {
|
|
467
|
+
}
|
|
468
|
+
try {
|
|
469
|
+
const { stdout, code } = await dockerExec("curl -sf http://localhost:4200/health");
|
|
470
|
+
status.serverHealthy = code === 0 && stdout.includes("ok");
|
|
471
|
+
if (status.serverHealthy) {
|
|
472
|
+
try {
|
|
473
|
+
const parsed = JSON.parse(stdout);
|
|
474
|
+
if (typeof parsed.version === "string") status.serverVersion = parsed.version;
|
|
475
|
+
} catch {
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
} catch {
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
const { stdout } = await dockerExec("df / --output=pcent | tail -1 | tr -dc '0-9'");
|
|
482
|
+
const pct = parseInt(stdout.trim(), 10);
|
|
483
|
+
if (!isNaN(pct)) status.diskUsagePercent = pct;
|
|
484
|
+
} catch {
|
|
485
|
+
}
|
|
486
|
+
try {
|
|
487
|
+
const { stdout } = await dockerExec("awk '{print int($1)}' /proc/uptime 2>/dev/null");
|
|
488
|
+
const up = parseInt(stdout.trim(), 10);
|
|
489
|
+
if (!isNaN(up)) status.uptimeSeconds = up;
|
|
490
|
+
} catch {
|
|
491
|
+
}
|
|
492
|
+
try {
|
|
493
|
+
const { stdout } = await dockerExec("cat /home/nexus/.nexus/tunnel-url.txt 2>/dev/null");
|
|
494
|
+
if (stdout.includes("https://")) {
|
|
495
|
+
status.tunnelUrl = stdout.trim();
|
|
496
|
+
}
|
|
497
|
+
} catch {
|
|
498
|
+
}
|
|
499
|
+
return status;
|
|
500
|
+
}
|
|
501
|
+
async function waitForServer(timeoutMs = 9e5) {
|
|
502
|
+
const start = Date.now();
|
|
503
|
+
let lastLog = 0;
|
|
504
|
+
let attempt = 0;
|
|
505
|
+
while (Date.now() - start < timeoutMs) {
|
|
506
|
+
try {
|
|
507
|
+
const { stdout, code } = await dockerExec("curl -sf http://localhost:4200/health");
|
|
508
|
+
if (code === 0 && stdout.includes("ok")) return true;
|
|
509
|
+
} catch {
|
|
510
|
+
}
|
|
511
|
+
const elapsed = Date.now() - start;
|
|
512
|
+
if (elapsed - lastLog >= 3e4) {
|
|
513
|
+
lastLog = elapsed;
|
|
514
|
+
try {
|
|
515
|
+
const { stdout } = await dockerExec("systemctl is-active nexus 2>/dev/null || echo 'starting...'");
|
|
516
|
+
process.stderr.write(`
|
|
517
|
+
[server ${Math.round(elapsed / 1e3)}s] ${stdout.trim().slice(0, 120)}
|
|
518
|
+
`);
|
|
519
|
+
} catch {
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const delay = backoffMs(attempt++);
|
|
523
|
+
const remaining = timeoutMs - (Date.now() - start);
|
|
524
|
+
if (remaining <= 0) break;
|
|
525
|
+
await new Promise((r) => setTimeout(r, Math.min(delay, remaining)));
|
|
526
|
+
}
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
var init_health = __esm({
|
|
530
|
+
"src/core/health.ts"() {
|
|
531
|
+
"use strict";
|
|
532
|
+
init_docker();
|
|
533
|
+
init_utils();
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
|
|
431
537
|
// src/core/docker.ts
|
|
432
538
|
var docker_exports = {};
|
|
433
539
|
__export(docker_exports, {
|
|
@@ -436,6 +542,7 @@ __export(docker_exports, {
|
|
|
436
542
|
installDocker: () => installDocker,
|
|
437
543
|
isDockerInstalled: () => isDockerInstalled,
|
|
438
544
|
isNexusRunning: () => isNexusRunning,
|
|
545
|
+
launchNexus: () => launchNexus,
|
|
439
546
|
pullImage: () => pullImage,
|
|
440
547
|
startBackend: () => startBackend,
|
|
441
548
|
startNexus: () => startNexus,
|
|
@@ -820,6 +927,16 @@ async function startBackend() {
|
|
|
820
927
|
child.unref();
|
|
821
928
|
log.success("Nexus backend process started");
|
|
822
929
|
}
|
|
930
|
+
async function launchNexus(keys, config, opts) {
|
|
931
|
+
const { healthTimeoutMs = 6e4, stopExisting = true } = opts ?? {};
|
|
932
|
+
if (stopExisting && await isNexusRunning()) {
|
|
933
|
+
await stopNexus();
|
|
934
|
+
}
|
|
935
|
+
await startNexus(keys, config);
|
|
936
|
+
if (healthTimeoutMs <= 0) return true;
|
|
937
|
+
const { waitForServer: waitForServer2 } = await Promise.resolve().then(() => (init_health(), health_exports));
|
|
938
|
+
return waitForServer2(healthTimeoutMs);
|
|
939
|
+
}
|
|
823
940
|
async function isNexusRunning() {
|
|
824
941
|
try {
|
|
825
942
|
const { stdout } = await execa("docker", [
|
|
@@ -1228,6 +1345,77 @@ function validateBackendUrl(url) {
|
|
|
1228
1345
|
};
|
|
1229
1346
|
}
|
|
1230
1347
|
|
|
1348
|
+
// src/core/api.ts
|
|
1349
|
+
function buildRunPayload(task, agentRole, agentGoal, keys) {
|
|
1350
|
+
const k = keys ?? loadApiKeys();
|
|
1351
|
+
return {
|
|
1352
|
+
task,
|
|
1353
|
+
agent_role: agentRole,
|
|
1354
|
+
agent_goal: agentGoal,
|
|
1355
|
+
api_key: k.anthropic || "",
|
|
1356
|
+
openai_api_key: k.openai || "",
|
|
1357
|
+
google_api_key: k.google || ""
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
async function httpPost(httpPort, path11, body, timeoutMs = 6e4) {
|
|
1361
|
+
const res = await fetch(`http://localhost:${httpPort}${path11}`, {
|
|
1362
|
+
method: "POST",
|
|
1363
|
+
headers: { "Content-Type": "application/json" },
|
|
1364
|
+
body: JSON.stringify(body),
|
|
1365
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
1366
|
+
});
|
|
1367
|
+
if (!res.ok) throw new Error(`Server returned ${res.status}`);
|
|
1368
|
+
const text = await res.text();
|
|
1369
|
+
try {
|
|
1370
|
+
const parsed = JSON.parse(text);
|
|
1371
|
+
return parsed.response ?? parsed.message ?? text;
|
|
1372
|
+
} catch {
|
|
1373
|
+
return text;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
async function httpGet(httpPort, path11, timeoutMs = 1e4) {
|
|
1377
|
+
try {
|
|
1378
|
+
const res = await fetch(`http://localhost:${httpPort}${path11}`, {
|
|
1379
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
1380
|
+
});
|
|
1381
|
+
const text = await res.text();
|
|
1382
|
+
return { ok: res.ok, text };
|
|
1383
|
+
} catch {
|
|
1384
|
+
return { ok: false, text: "" };
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
async function checkServerHealth(target, timeoutMs = 1e4) {
|
|
1388
|
+
const url = typeof target === "number" ? `http://localhost:${target}/health` : `${target}/health`;
|
|
1389
|
+
try {
|
|
1390
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
1391
|
+
return res.ok;
|
|
1392
|
+
} catch {
|
|
1393
|
+
return false;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// src/core/sse-parser.ts
|
|
1398
|
+
async function* parseSSEStream(reader) {
|
|
1399
|
+
const decoder = new TextDecoder();
|
|
1400
|
+
let buffer = "";
|
|
1401
|
+
while (true) {
|
|
1402
|
+
const { done, value } = await reader.read();
|
|
1403
|
+
if (done) break;
|
|
1404
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1405
|
+
const lines = buffer.split("\n");
|
|
1406
|
+
buffer = lines.pop() || "";
|
|
1407
|
+
for (const line of lines) {
|
|
1408
|
+
if (!line.startsWith("data: ")) continue;
|
|
1409
|
+
try {
|
|
1410
|
+
const parsed = JSON.parse(line.slice(6));
|
|
1411
|
+
yield parsed;
|
|
1412
|
+
} catch (e) {
|
|
1413
|
+
if (process.env.LOG_LEVEL === "debug") console.error("SSE parse error:", e);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1231
1419
|
// src/cli/run-command.ts
|
|
1232
1420
|
async function runCommand(task, options) {
|
|
1233
1421
|
const backendUrl = process.env.BACKEND_URL || "http://localhost:4200";
|
|
@@ -1240,30 +1428,17 @@ ${urlCheck.error}`);
|
|
|
1240
1428
|
tui.displayHeader(task, options.agent);
|
|
1241
1429
|
tui.displayConnecting();
|
|
1242
1430
|
try {
|
|
1243
|
-
|
|
1244
|
-
try {
|
|
1245
|
-
const healthResponse = await fetch(`${backendUrl}/health`);
|
|
1246
|
-
healthOk = healthResponse.ok;
|
|
1247
|
-
} catch {
|
|
1248
|
-
}
|
|
1249
|
-
if (!healthOk) {
|
|
1431
|
+
if (!await checkServerHealth(backendUrl)) {
|
|
1250
1432
|
console.error(
|
|
1251
1433
|
"Backend not responding. Start it with:\n buildwithnexus server"
|
|
1252
1434
|
);
|
|
1253
1435
|
process.exit(1);
|
|
1254
1436
|
}
|
|
1255
|
-
const
|
|
1437
|
+
const payload = buildRunPayload(task, options.agent, options.goal || "");
|
|
1256
1438
|
const response = await fetch(`${backendUrl}/api/run`, {
|
|
1257
1439
|
method: "POST",
|
|
1258
1440
|
headers: { "Content-Type": "application/json" },
|
|
1259
|
-
body: JSON.stringify(
|
|
1260
|
-
task,
|
|
1261
|
-
agent_role: options.agent,
|
|
1262
|
-
agent_goal: options.goal || "",
|
|
1263
|
-
api_key: keys.anthropic || "",
|
|
1264
|
-
openai_api_key: keys.openai || "",
|
|
1265
|
-
google_api_key: keys.google || ""
|
|
1266
|
-
})
|
|
1441
|
+
body: JSON.stringify(payload)
|
|
1267
1442
|
});
|
|
1268
1443
|
if (!response.ok) {
|
|
1269
1444
|
console.error("Backend error");
|
|
@@ -1415,32 +1590,13 @@ function classifyIntent(task) {
|
|
|
1415
1590
|
|
|
1416
1591
|
// src/cli/interactive.ts
|
|
1417
1592
|
init_secrets();
|
|
1593
|
+
init_docker();
|
|
1418
1594
|
|
|
1419
|
-
// src/core/
|
|
1420
|
-
|
|
1421
|
-
const decoder = new TextDecoder();
|
|
1422
|
-
let buffer = "";
|
|
1423
|
-
while (true) {
|
|
1424
|
-
const { done, value } = await reader.read();
|
|
1425
|
-
if (done) break;
|
|
1426
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1427
|
-
const lines = buffer.split("\n");
|
|
1428
|
-
buffer = lines.pop() || "";
|
|
1429
|
-
for (const line of lines) {
|
|
1430
|
-
if (!line.startsWith("data: ")) continue;
|
|
1431
|
-
try {
|
|
1432
|
-
const parsed = JSON.parse(line.slice(6));
|
|
1433
|
-
yield parsed;
|
|
1434
|
-
} catch (e) {
|
|
1435
|
-
if (process.env.LOG_LEVEL === "debug") console.error("SSE parse error:", e);
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1595
|
+
// src/core/version.ts
|
|
1596
|
+
var resolvedVersion = true ? "0.7.3" : pkg.version;
|
|
1440
1597
|
|
|
1441
1598
|
// src/cli/interactive.ts
|
|
1442
|
-
|
|
1443
|
-
var appVersion = true ? "0.7.2" : pkg.version;
|
|
1599
|
+
var appVersion = resolvedVersion;
|
|
1444
1600
|
async function interactiveMode() {
|
|
1445
1601
|
const backendUrl = process.env.BACKEND_URL || "http://localhost:4200";
|
|
1446
1602
|
const urlCheck = validateBackendUrl(backendUrl);
|
|
@@ -1479,26 +1635,11 @@ ${urlCheck.error}`));
|
|
|
1479
1635
|
async function waitForBackend() {
|
|
1480
1636
|
for (let i = 0; i < 5; i++) {
|
|
1481
1637
|
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
1482
|
-
|
|
1483
|
-
const retryResponse = await fetch(`${backendUrl}/health`, { signal: AbortSignal.timeout(1e4) });
|
|
1484
|
-
if (retryResponse.ok) return true;
|
|
1485
|
-
} catch {
|
|
1486
|
-
}
|
|
1638
|
+
if (await checkServerHealth(backendUrl)) return true;
|
|
1487
1639
|
}
|
|
1488
1640
|
return false;
|
|
1489
1641
|
}
|
|
1490
|
-
|
|
1491
|
-
const response = await fetch(`${backendUrl}/health`, { signal: AbortSignal.timeout(1e4) });
|
|
1492
|
-
if (!response.ok) {
|
|
1493
|
-
console.log(chalk3.yellow("\u26A0\uFE0F Backend not responding, starting..."));
|
|
1494
|
-
await startBackend();
|
|
1495
|
-
const ready = await waitForBackend();
|
|
1496
|
-
if (!ready) {
|
|
1497
|
-
console.error(chalk3.red("\u274C Backend failed to start. Run: buildwithnexus server"));
|
|
1498
|
-
process.exit(1);
|
|
1499
|
-
}
|
|
1500
|
-
}
|
|
1501
|
-
} catch {
|
|
1642
|
+
if (!await checkServerHealth(backendUrl)) {
|
|
1502
1643
|
console.log(chalk3.yellow("\u26A0\uFE0F Backend not accessible, attempting to start..."));
|
|
1503
1644
|
await startBackend();
|
|
1504
1645
|
const ready = await waitForBackend();
|
|
@@ -1619,12 +1760,11 @@ async function planModeLoop(task, backendUrl, ask) {
|
|
|
1619
1760
|
console.log("");
|
|
1620
1761
|
console.log(chalk3.yellow("\u23F3 Fetching plan from backend..."));
|
|
1621
1762
|
let steps = [];
|
|
1622
|
-
const keys = loadApiKeys();
|
|
1623
1763
|
try {
|
|
1624
1764
|
const response = await fetch(`${backendUrl}/api/run`, {
|
|
1625
1765
|
method: "POST",
|
|
1626
1766
|
headers: { "Content-Type": "application/json" },
|
|
1627
|
-
body: JSON.stringify(
|
|
1767
|
+
body: JSON.stringify(buildRunPayload(task, "engineer", "")),
|
|
1628
1768
|
signal: AbortSignal.timeout(12e4)
|
|
1629
1769
|
});
|
|
1630
1770
|
if (!response.ok) {
|
|
@@ -1637,7 +1777,7 @@ async function planModeLoop(task, backendUrl, ask) {
|
|
|
1637
1777
|
const reader = streamResponse.body?.getReader();
|
|
1638
1778
|
if (!reader) throw new Error("No response body");
|
|
1639
1779
|
let planReceived = false;
|
|
1640
|
-
for await (const parsed of
|
|
1780
|
+
for await (const parsed of parseSSEStream(reader)) {
|
|
1641
1781
|
if (parsed.type === "plan") {
|
|
1642
1782
|
steps = parsed.data["steps"] || [];
|
|
1643
1783
|
planReceived = true;
|
|
@@ -1710,12 +1850,11 @@ async function editPlanSteps(steps, ask) {
|
|
|
1710
1850
|
async function buildModeLoop(task, backendUrl, ask) {
|
|
1711
1851
|
console.log(chalk3.bold("Task:"), chalk3.white(task));
|
|
1712
1852
|
tui.displayConnecting();
|
|
1713
|
-
const keys = loadApiKeys();
|
|
1714
1853
|
try {
|
|
1715
1854
|
const response = await fetch(`${backendUrl}/api/run`, {
|
|
1716
1855
|
method: "POST",
|
|
1717
1856
|
headers: { "Content-Type": "application/json" },
|
|
1718
|
-
body: JSON.stringify(
|
|
1857
|
+
body: JSON.stringify(buildRunPayload(task, "engineer", "")),
|
|
1719
1858
|
signal: AbortSignal.timeout(12e4)
|
|
1720
1859
|
});
|
|
1721
1860
|
if (!response.ok) {
|
|
@@ -1729,7 +1868,7 @@ async function buildModeLoop(task, backendUrl, ask) {
|
|
|
1729
1868
|
const streamResponse = await fetch(`${backendUrl}/api/stream/${run_id}`, { signal: AbortSignal.timeout(12e4) });
|
|
1730
1869
|
const reader = streamResponse.body?.getReader();
|
|
1731
1870
|
if (!reader) throw new Error("No response body");
|
|
1732
|
-
for await (const parsed of
|
|
1871
|
+
for await (const parsed of parseSSEStream(reader)) {
|
|
1733
1872
|
const type = parsed.type;
|
|
1734
1873
|
if (type === "execution_complete") {
|
|
1735
1874
|
const summary = parsed.data["summary"] || "";
|
|
@@ -1768,18 +1907,14 @@ async function brainstormModeLoop(task, backendUrl, ask) {
|
|
|
1768
1907
|
while (true) {
|
|
1769
1908
|
console.log(chalk3.bold.blue("\u{1F4A1} Thinking..."));
|
|
1770
1909
|
try {
|
|
1771
|
-
const keys = loadApiKeys();
|
|
1772
1910
|
const response = await fetch(`${backendUrl}/api/run`, {
|
|
1773
1911
|
method: "POST",
|
|
1774
1912
|
headers: { "Content-Type": "application/json" },
|
|
1775
|
-
body: JSON.stringify(
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
openai_api_key: keys.openai || "",
|
|
1781
|
-
google_api_key: keys.google || ""
|
|
1782
|
-
}),
|
|
1913
|
+
body: JSON.stringify(buildRunPayload(
|
|
1914
|
+
currentQuestion,
|
|
1915
|
+
"brainstorm",
|
|
1916
|
+
"Generate ideas, considerations, and suggestions. Be concise and helpful."
|
|
1917
|
+
)),
|
|
1783
1918
|
signal: AbortSignal.timeout(12e4)
|
|
1784
1919
|
});
|
|
1785
1920
|
if (response.ok) {
|
|
@@ -1788,7 +1923,7 @@ async function brainstormModeLoop(task, backendUrl, ask) {
|
|
|
1788
1923
|
const reader = streamResponse.body?.getReader();
|
|
1789
1924
|
if (reader) {
|
|
1790
1925
|
let responseText = "";
|
|
1791
|
-
for await (const parsed of
|
|
1926
|
+
for await (const parsed of parseSSEStream(reader)) {
|
|
1792
1927
|
const type = parsed.type;
|
|
1793
1928
|
const data = parsed.data;
|
|
1794
1929
|
if (type === "done" || type === "execution_complete") {
|
|
@@ -2021,26 +2156,6 @@ async function withSpinner(spinner, label, fn) {
|
|
|
2021
2156
|
succeed(spinner, label);
|
|
2022
2157
|
return result;
|
|
2023
2158
|
}
|
|
2024
|
-
async function waitForHealthy(port, timeoutMs = 12e4) {
|
|
2025
|
-
const start = Date.now();
|
|
2026
|
-
let attempt = 0;
|
|
2027
|
-
const backoffMs = (n) => Math.min(2e3 * Math.pow(2, n), 1e4);
|
|
2028
|
-
while (Date.now() - start < timeoutMs) {
|
|
2029
|
-
try {
|
|
2030
|
-
const res = await fetch(`http://localhost:${port}/health`);
|
|
2031
|
-
if (res.ok) {
|
|
2032
|
-
const body = await res.text();
|
|
2033
|
-
if (body.includes("ok")) return true;
|
|
2034
|
-
}
|
|
2035
|
-
} catch {
|
|
2036
|
-
}
|
|
2037
|
-
const delay = backoffMs(attempt++);
|
|
2038
|
-
const remaining = timeoutMs - (Date.now() - start);
|
|
2039
|
-
if (remaining <= 0) break;
|
|
2040
|
-
await new Promise((r) => setTimeout(r, Math.min(delay, remaining)));
|
|
2041
|
-
}
|
|
2042
|
-
return false;
|
|
2043
|
-
}
|
|
2044
2159
|
var phases = [
|
|
2045
2160
|
// Phase 1 — Configuration (~30s)
|
|
2046
2161
|
{
|
|
@@ -2120,19 +2235,16 @@ var phases = [
|
|
|
2120
2235
|
name: "Launch",
|
|
2121
2236
|
run: async (ctx, spinner) => {
|
|
2122
2237
|
const { config, keys } = ctx;
|
|
2123
|
-
const alreadyRunning = await isNexusRunning();
|
|
2124
|
-
if (alreadyRunning) {
|
|
2125
|
-
await withSpinner(spinner, "Stopping existing NEXUS container...", () => stopNexus());
|
|
2126
|
-
}
|
|
2127
2238
|
await withSpinner(
|
|
2128
2239
|
spinner,
|
|
2129
2240
|
"Starting NEXUS container...",
|
|
2130
|
-
() =>
|
|
2241
|
+
() => launchNexus(
|
|
2131
2242
|
{
|
|
2132
2243
|
anthropic: keys.ANTHROPIC_API_KEY,
|
|
2133
2244
|
openai: keys.OPENAI_API_KEY || ""
|
|
2134
2245
|
},
|
|
2135
|
-
{ port: config.httpPort }
|
|
2246
|
+
{ port: config.httpPort },
|
|
2247
|
+
{ healthTimeoutMs: 0, stopExisting: true }
|
|
2136
2248
|
)
|
|
2137
2249
|
);
|
|
2138
2250
|
ctx.containerStarted = true;
|
|
@@ -2145,7 +2257,8 @@ var phases = [
|
|
|
2145
2257
|
const { config } = ctx;
|
|
2146
2258
|
spinner.text = `Waiting for NEXUS server on port ${config.httpPort}...`;
|
|
2147
2259
|
spinner.start();
|
|
2148
|
-
const
|
|
2260
|
+
const { waitForServer: waitForServer2 } = await Promise.resolve().then(() => (init_health(), health_exports));
|
|
2261
|
+
const healthy = await waitForServer2(12e4);
|
|
2149
2262
|
if (!healthy) {
|
|
2150
2263
|
fail(spinner, "Server failed to start within 120s");
|
|
2151
2264
|
log.warn("Check logs: docker logs nexus");
|
|
@@ -2231,40 +2344,6 @@ import { Command as Command3 } from "commander";
|
|
|
2231
2344
|
init_logger();
|
|
2232
2345
|
init_secrets();
|
|
2233
2346
|
init_docker();
|
|
2234
|
-
|
|
2235
|
-
// src/core/health.ts
|
|
2236
|
-
init_docker();
|
|
2237
|
-
async function waitForServer(timeoutMs = 9e5) {
|
|
2238
|
-
const start = Date.now();
|
|
2239
|
-
let lastLog = 0;
|
|
2240
|
-
let attempt = 0;
|
|
2241
|
-
const backoffMs = (n) => Math.min(3e3 * Math.pow(2, n), 3e4);
|
|
2242
|
-
while (Date.now() - start < timeoutMs) {
|
|
2243
|
-
try {
|
|
2244
|
-
const { stdout, code } = await dockerExec("curl -sf http://localhost:4200/health");
|
|
2245
|
-
if (code === 0 && stdout.includes("ok")) return true;
|
|
2246
|
-
} catch {
|
|
2247
|
-
}
|
|
2248
|
-
const elapsed = Date.now() - start;
|
|
2249
|
-
if (elapsed - lastLog >= 3e4) {
|
|
2250
|
-
lastLog = elapsed;
|
|
2251
|
-
try {
|
|
2252
|
-
const { stdout } = await dockerExec("systemctl is-active nexus 2>/dev/null || echo 'starting...'");
|
|
2253
|
-
process.stderr.write(`
|
|
2254
|
-
[server ${Math.round(elapsed / 1e3)}s] ${stdout.trim().slice(0, 120)}
|
|
2255
|
-
`);
|
|
2256
|
-
} catch {
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
|
-
const delay = backoffMs(attempt++);
|
|
2260
|
-
const remaining = timeoutMs - (Date.now() - start);
|
|
2261
|
-
if (remaining <= 0) break;
|
|
2262
|
-
await new Promise((r) => setTimeout(r, Math.min(delay, remaining)));
|
|
2263
|
-
}
|
|
2264
|
-
return false;
|
|
2265
|
-
}
|
|
2266
|
-
|
|
2267
|
-
// src/commands/start.ts
|
|
2268
2347
|
var startCommand = new Command3("start").description("Start the NEXUS runtime").action(async () => {
|
|
2269
2348
|
const config = loadConfig();
|
|
2270
2349
|
if (!config) {
|
|
@@ -2279,21 +2358,17 @@ var startCommand = new Command3("start").description("Start the NEXUS runtime").
|
|
|
2279
2358
|
spinner.start();
|
|
2280
2359
|
await pullImage("buildwithnexus/nexus", "latest");
|
|
2281
2360
|
succeed(spinner, "Image ready");
|
|
2282
|
-
spinner = createSpinner("Starting NEXUS container...");
|
|
2283
|
-
spinner.start();
|
|
2284
2361
|
const keys = loadKeys();
|
|
2285
2362
|
if (!keys) {
|
|
2286
|
-
|
|
2363
|
+
log.error("No API keys found. Run: buildwithnexus init");
|
|
2287
2364
|
process.exit(1);
|
|
2288
2365
|
}
|
|
2289
|
-
|
|
2366
|
+
spinner = createSpinner("Starting NEXUS container...");
|
|
2367
|
+
spinner.start();
|
|
2368
|
+
const ok = await launchNexus(
|
|
2290
2369
|
{ anthropic: keys.ANTHROPIC_API_KEY, openai: keys.OPENAI_API_KEY || "" },
|
|
2291
2370
|
{ port: config.httpPort }
|
|
2292
2371
|
);
|
|
2293
|
-
succeed(spinner, "Container started");
|
|
2294
|
-
spinner = createSpinner("Waiting for NEXUS server...");
|
|
2295
|
-
spinner.start();
|
|
2296
|
-
const ok = await waitForServer(6e4);
|
|
2297
2372
|
if (ok) {
|
|
2298
2373
|
succeed(spinner, "NEXUS server running");
|
|
2299
2374
|
} else {
|
|
@@ -2314,13 +2389,11 @@ var startCommand = new Command3("start").description("Start the NEXUS runtime").
|
|
|
2314
2389
|
|
|
2315
2390
|
// src/commands/stop.ts
|
|
2316
2391
|
import { Command as Command4 } from "commander";
|
|
2392
|
+
import { execa as execa3 } from "execa";
|
|
2317
2393
|
init_logger();
|
|
2318
|
-
import { execFile } from "child_process";
|
|
2319
|
-
import { promisify } from "util";
|
|
2320
|
-
var execFileAsync = promisify(execFile);
|
|
2321
2394
|
async function containerExists() {
|
|
2322
2395
|
try {
|
|
2323
|
-
const { stdout } = await
|
|
2396
|
+
const { stdout } = await execa3("docker", [
|
|
2324
2397
|
"ps",
|
|
2325
2398
|
"-a",
|
|
2326
2399
|
"--filter",
|
|
@@ -2335,7 +2408,7 @@ async function containerExists() {
|
|
|
2335
2408
|
}
|
|
2336
2409
|
async function isContainerRunning() {
|
|
2337
2410
|
try {
|
|
2338
|
-
const { stdout } = await
|
|
2411
|
+
const { stdout } = await execa3("docker", [
|
|
2339
2412
|
"ps",
|
|
2340
2413
|
"--filter",
|
|
2341
2414
|
"name=^nexus$",
|
|
@@ -2359,10 +2432,10 @@ var stopCommand = new Command4("stop").description("Gracefully shut down the NEX
|
|
|
2359
2432
|
try {
|
|
2360
2433
|
if (await isContainerRunning()) {
|
|
2361
2434
|
spinner.text = "Stopping container...";
|
|
2362
|
-
await
|
|
2435
|
+
await execa3("docker", ["stop", "nexus"]);
|
|
2363
2436
|
}
|
|
2364
2437
|
spinner.text = "Removing container...";
|
|
2365
|
-
await
|
|
2438
|
+
await execa3("docker", ["rm", "nexus"]);
|
|
2366
2439
|
succeed(spinner, "NEXUS container stopped and removed");
|
|
2367
2440
|
} catch (err) {
|
|
2368
2441
|
fail(spinner, "Failed to stop NEXUS container");
|
|
@@ -2506,11 +2579,11 @@ var doctorCommand = new Command6("doctor").description("Diagnose NEXUS runtime e
|
|
|
2506
2579
|
// src/commands/logs.ts
|
|
2507
2580
|
init_logger();
|
|
2508
2581
|
import { Command as Command7 } from "commander";
|
|
2509
|
-
import { execa as
|
|
2582
|
+
import { execa as execa4 } from "execa";
|
|
2510
2583
|
var logsCommand = new Command7("logs").description("View NEXUS server logs").action(async () => {
|
|
2511
2584
|
let containerExists2 = false;
|
|
2512
2585
|
try {
|
|
2513
|
-
const { stdout } = await
|
|
2586
|
+
const { stdout } = await execa4("docker", [
|
|
2514
2587
|
"ps",
|
|
2515
2588
|
"-a",
|
|
2516
2589
|
"--filter",
|
|
@@ -2527,7 +2600,7 @@ var logsCommand = new Command7("logs").description("View NEXUS server logs").act
|
|
|
2527
2600
|
log.error("NEXUS container not found. Start with: buildwithnexus start");
|
|
2528
2601
|
process.exit(1);
|
|
2529
2602
|
}
|
|
2530
|
-
const proc =
|
|
2603
|
+
const proc = execa4("docker", ["logs", "-f", "nexus"], {
|
|
2531
2604
|
stdout: "pipe",
|
|
2532
2605
|
stderr: "pipe"
|
|
2533
2606
|
});
|
|
@@ -2544,7 +2617,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
2544
2617
|
init_logger();
|
|
2545
2618
|
init_secrets();
|
|
2546
2619
|
init_docker();
|
|
2547
|
-
import { execa as
|
|
2620
|
+
import { execa as execa5 } from "execa";
|
|
2548
2621
|
function getReleaseTarball() {
|
|
2549
2622
|
const dir = path6.dirname(fileURLToPath2(import.meta.url));
|
|
2550
2623
|
const tarballPath = path6.join(dir, "nexus-release.tar.gz");
|
|
@@ -2566,7 +2639,7 @@ var updateCommand = new Command8("update").description("Update NEXUS to the late
|
|
|
2566
2639
|
let spinner = createSpinner("Uploading release tarball...");
|
|
2567
2640
|
spinner.start();
|
|
2568
2641
|
const tarballPath = getReleaseTarball();
|
|
2569
|
-
await
|
|
2642
|
+
await execa5("docker", ["cp", tarballPath, "nexus:/tmp/nexus-release.tar.gz"]);
|
|
2570
2643
|
succeed(spinner, "Tarball uploaded");
|
|
2571
2644
|
spinner = createSpinner("Stopping NEXUS server...");
|
|
2572
2645
|
spinner.start();
|
|
@@ -2720,7 +2793,7 @@ keysCommand.command("set <key>").description("Set or update an API key (e.g. ANT
|
|
|
2720
2793
|
init_logger();
|
|
2721
2794
|
init_docker();
|
|
2722
2795
|
import { Command as Command11 } from "commander";
|
|
2723
|
-
import { execa as
|
|
2796
|
+
import { execa as execa6 } from "execa";
|
|
2724
2797
|
var sshCommand = new Command11("ssh").description("Open an interactive shell inside the NEXUS container").action(async () => {
|
|
2725
2798
|
const running = await isNexusRunning();
|
|
2726
2799
|
if (!running) {
|
|
@@ -2728,7 +2801,7 @@ var sshCommand = new Command11("ssh").description("Open an interactive shell ins
|
|
|
2728
2801
|
process.exit(1);
|
|
2729
2802
|
}
|
|
2730
2803
|
log.dim("Opening shell in NEXUS container...");
|
|
2731
|
-
await
|
|
2804
|
+
await execa6("docker", ["exec", "-it", "nexus", "/bin/bash"], { stdio: "inherit" });
|
|
2732
2805
|
});
|
|
2733
2806
|
|
|
2734
2807
|
// src/commands/brainstorm.ts
|
|
@@ -2776,13 +2849,7 @@ var brainstormCommand = new Command12("brainstorm").description("Brainstorm an i
|
|
|
2776
2849
|
}
|
|
2777
2850
|
const spinner = createSpinner("Connecting to NEXUS...");
|
|
2778
2851
|
spinner.start();
|
|
2779
|
-
|
|
2780
|
-
try {
|
|
2781
|
-
const healthRes = await fetch(`http://localhost:${config.httpPort}/health`);
|
|
2782
|
-
const healthText = await healthRes.text();
|
|
2783
|
-
healthOk = healthRes.ok && healthText.includes("ok");
|
|
2784
|
-
} catch {
|
|
2785
|
-
}
|
|
2852
|
+
const healthOk = await checkServerHealth(config.httpPort);
|
|
2786
2853
|
if (!healthOk) {
|
|
2787
2854
|
fail(spinner, "NEXUS server is not healthy");
|
|
2788
2855
|
log.warn("Check status: buildwithnexus status");
|
|
@@ -3303,33 +3370,6 @@ var EventStream = class {
|
|
|
3303
3370
|
};
|
|
3304
3371
|
|
|
3305
3372
|
// src/commands/shell.ts
|
|
3306
|
-
async function httpPost(httpPort, path11, body) {
|
|
3307
|
-
const res = await fetch(`http://localhost:${httpPort}${path11}`, {
|
|
3308
|
-
method: "POST",
|
|
3309
|
-
headers: { "Content-Type": "application/json" },
|
|
3310
|
-
body: JSON.stringify(body),
|
|
3311
|
-
signal: AbortSignal.timeout(6e4)
|
|
3312
|
-
});
|
|
3313
|
-
if (!res.ok) throw new Error(`Server returned ${res.status}`);
|
|
3314
|
-
const text = await res.text();
|
|
3315
|
-
try {
|
|
3316
|
-
const parsed = JSON.parse(text);
|
|
3317
|
-
return parsed.response ?? parsed.message ?? text;
|
|
3318
|
-
} catch {
|
|
3319
|
-
return text;
|
|
3320
|
-
}
|
|
3321
|
-
}
|
|
3322
|
-
async function httpGet(httpPort, path11) {
|
|
3323
|
-
try {
|
|
3324
|
-
const res = await fetch(`http://localhost:${httpPort}${path11}`, {
|
|
3325
|
-
signal: AbortSignal.timeout(1e4)
|
|
3326
|
-
});
|
|
3327
|
-
const text = await res.text();
|
|
3328
|
-
return { ok: res.ok, text };
|
|
3329
|
-
} catch {
|
|
3330
|
-
return { ok: false, text: "" };
|
|
3331
|
-
}
|
|
3332
|
-
}
|
|
3333
3373
|
async function sendMessage2(httpPort, message) {
|
|
3334
3374
|
return httpPost(httpPort, "/message", { message, source: "shell" });
|
|
3335
3375
|
}
|
|
@@ -3563,12 +3603,12 @@ var shellCommand2 = new Command14("shell").description("Launch the interactive N
|
|
|
3563
3603
|
name: "logs",
|
|
3564
3604
|
description: "Show recent container logs",
|
|
3565
3605
|
handler: async () => {
|
|
3566
|
-
const { execa:
|
|
3606
|
+
const { execa: execa7 } = await import("execa");
|
|
3567
3607
|
console.log("");
|
|
3568
3608
|
console.log(chalk16.bold(" Recent Logs:"));
|
|
3569
3609
|
console.log(chalk16.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
3570
3610
|
try {
|
|
3571
|
-
const { stdout } = await
|
|
3611
|
+
const { stdout } = await execa7("docker", ["logs", "--tail", "30", "nexus"]);
|
|
3572
3612
|
for (const line of redact(stdout).split("\n")) {
|
|
3573
3613
|
console.log(chalk16.dim(" " + line));
|
|
3574
3614
|
}
|
|
@@ -3582,10 +3622,10 @@ var shellCommand2 = new Command14("shell").description("Launch the interactive N
|
|
|
3582
3622
|
name: "exec",
|
|
3583
3623
|
description: "Drop into the container shell for debugging/inspection",
|
|
3584
3624
|
handler: async () => {
|
|
3585
|
-
const { execa:
|
|
3625
|
+
const { execa: execa7 } = await import("execa");
|
|
3586
3626
|
eventStream.stop();
|
|
3587
3627
|
try {
|
|
3588
|
-
await
|
|
3628
|
+
await execa7("docker", ["exec", "-it", "nexus", "/bin/sh"], { stdio: "inherit" });
|
|
3589
3629
|
} catch {
|
|
3590
3630
|
}
|
|
3591
3631
|
eventStream.start();
|
|
@@ -3845,7 +3885,7 @@ import os4 from "os";
|
|
|
3845
3885
|
import path10 from "path";
|
|
3846
3886
|
var homeEnvPath = path10.join(os4.homedir(), ".env.local");
|
|
3847
3887
|
dotenv2.config({ path: homeEnvPath });
|
|
3848
|
-
var version =
|
|
3888
|
+
var version = resolvedVersion;
|
|
3849
3889
|
checkForUpdates(version);
|
|
3850
3890
|
program.name("buildwithnexus").description("Nexus - AI-Powered Task Execution").version(version);
|
|
3851
3891
|
program.command("da-init").description("Initialize Nexus (set up API keys and .env.local)").action(deepAgentsInitCommand);
|
package/dist/deep-agents-bin.js
CHANGED
|
@@ -231,6 +231,19 @@ function loadApiKeys() {
|
|
|
231
231
|
return result;
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
// src/core/api.ts
|
|
235
|
+
function buildRunPayload(task, agentRole, agentGoal, keys) {
|
|
236
|
+
const k = keys ?? loadApiKeys();
|
|
237
|
+
return {
|
|
238
|
+
task,
|
|
239
|
+
agent_role: agentRole,
|
|
240
|
+
agent_goal: agentGoal,
|
|
241
|
+
api_key: k.anthropic || "",
|
|
242
|
+
openai_api_key: k.openai || "",
|
|
243
|
+
google_api_key: k.google || ""
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
234
247
|
// src/core/models.ts
|
|
235
248
|
var MODELS = {
|
|
236
249
|
OPUS: "claude-opus-4-6",
|
|
@@ -256,18 +269,11 @@ Starting Nexus Workflow
|
|
|
256
269
|
console.log(` Backend: ${backendUrl}
|
|
257
270
|
`);
|
|
258
271
|
try {
|
|
259
|
-
const
|
|
272
|
+
const payload = buildRunPayload(task, options.agent, options.goal || "");
|
|
260
273
|
const response = await fetch(`${backendUrl}/api/run`, {
|
|
261
274
|
method: "POST",
|
|
262
275
|
headers: { "Content-Type": "application/json" },
|
|
263
|
-
body: JSON.stringify(
|
|
264
|
-
task,
|
|
265
|
-
agent_role: options.agent,
|
|
266
|
-
agent_goal: options.goal || "",
|
|
267
|
-
api_key: keys.anthropic || "",
|
|
268
|
-
openai_api_key: keys.openai || "",
|
|
269
|
-
google_api_key: keys.google || ""
|
|
270
|
-
})
|
|
276
|
+
body: JSON.stringify(payload)
|
|
271
277
|
});
|
|
272
278
|
const { run_id } = await response.json();
|
|
273
279
|
console.log(`Run ID: ${run_id}
|