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 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
- let healthOk = false;
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 keys = loadApiKeys();
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/sse-parser.ts
1420
- async function* parseSSEStream2(reader) {
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
- init_docker();
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
- try {
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
- try {
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({ task, agent_role: "engineer", agent_goal: "", api_key: keys.anthropic || "", openai_api_key: keys.openai || "", google_api_key: keys.google || "" }),
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 parseSSEStream2(reader)) {
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({ task, agent_role: "engineer", agent_goal: "", api_key: keys.anthropic || "", openai_api_key: keys.openai || "", google_api_key: keys.google || "" }),
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 parseSSEStream2(reader)) {
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
- task: currentQuestion,
1777
- agent_role: "brainstorm",
1778
- agent_goal: "Generate ideas, considerations, and suggestions. Be concise and helpful.",
1779
- api_key: keys.anthropic || "",
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 parseSSEStream2(reader)) {
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
- () => startNexus(
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 healthy = await waitForHealthy(config.httpPort);
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
- fail(spinner, "No API keys found. Run: buildwithnexus init");
2363
+ log.error("No API keys found. Run: buildwithnexus init");
2287
2364
  process.exit(1);
2288
2365
  }
2289
- await startNexus(
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 execFileAsync("docker", [
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 execFileAsync("docker", [
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 execFileAsync("docker", ["stop", "nexus"]);
2435
+ await execa3("docker", ["stop", "nexus"]);
2363
2436
  }
2364
2437
  spinner.text = "Removing container...";
2365
- await execFileAsync("docker", ["rm", "nexus"]);
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 execa3 } from "execa";
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 execa3("docker", [
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 = execa3("docker", ["logs", "-f", "nexus"], {
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 execa4 } from "execa";
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 execa4("docker", ["cp", tarballPath, "nexus:/tmp/nexus-release.tar.gz"]);
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 execa5 } from "execa";
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 execa5("docker", ["exec", "-it", "nexus", "/bin/bash"], { stdio: "inherit" });
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
- let healthOk = false;
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: execa6 } = await import("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 execa6("docker", ["logs", "--tail", "30", "nexus"]);
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: execa6 } = await import("execa");
3625
+ const { execa: execa7 } = await import("execa");
3586
3626
  eventStream.stop();
3587
3627
  try {
3588
- await execa6("docker", ["exec", "-it", "nexus", "/bin/sh"], { stdio: "inherit" });
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 = true ? "0.7.2" : pkg.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);
@@ -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 keys = loadApiKeys();
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}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buildwithnexus",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "description": "Interactive AI agent orchestrator with intent-based planning, execution, and brainstorming modes",
5
5
  "type": "module",
6
6
  "bin": {