buildwithnexus 0.4.6 → 0.4.8

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
@@ -18,11 +18,9 @@ __export(banner_exports, {
18
18
  showSecurityPosture: () => showSecurityPosture
19
19
  });
20
20
  import chalk from "chalk";
21
- import { createRequire } from "module";
22
21
  function showBanner() {
23
22
  console.log(BANNER);
24
- console.log(chalk.dim(` v${version} \xB7 buildwithnexus.dev
25
- `));
23
+ console.log(chalk.dim(" v0.3.1 \xB7 buildwithnexus.dev\n"));
26
24
  }
27
25
  function showPhase(phase, total, description) {
28
26
  const progress = chalk.cyan(`[${phase}/${total}]`);
@@ -75,12 +73,10 @@ function showCompletion(urls) {
75
73
  );
76
74
  console.log(lines.join("\n"));
77
75
  }
78
- var _require, version, BANNER;
76
+ var BANNER;
79
77
  var init_banner = __esm({
80
78
  "src/ui/banner.ts"() {
81
79
  "use strict";
82
- _require = createRequire(import.meta.url);
83
- ({ version } = _require("../package.json"));
84
80
  BANNER = `
85
81
  \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
86
82
  \u2551 ${chalk.bold.cyan("B U I L D W I T H N E X U S")} \u2551
@@ -345,9 +341,9 @@ var init_secrets = __esm({
345
341
  "src/core/secrets.ts"() {
346
342
  "use strict";
347
343
  init_dlp();
348
- NEXUS_HOME2 = path2.join(process.env.HOME || "~", ".buildwithnexus");
349
- CONFIG_PATH = path2.join(NEXUS_HOME2, "config.json");
350
- KEYS_PATH = path2.join(NEXUS_HOME2, ".env.keys");
344
+ NEXUS_HOME2 = process.env.NEXUS_HOME || path2.join(process.env.HOME || "~", ".buildwithnexus");
345
+ CONFIG_PATH = process.env.NEXUS_CONFIG_PATH || path2.join(NEXUS_HOME2, "config.json");
346
+ KEYS_PATH = process.env.NEXUS_KEYS_PATH || path2.join(NEXUS_HOME2, ".env.keys");
351
347
  }
352
348
  });
353
349
 
@@ -528,10 +524,8 @@ async function launchVm(platform, diskPath, initIsoPath, ram, cpus, ports) {
528
524
  ];
529
525
  try {
530
526
  await execa(platform.qemuBinary, buildArgs(platform.qemuCpuFlag.split(" ")), { env: scrubEnv() });
531
- process.stderr.write(" [vm] Hardware acceleration (HVF) active\n");
532
527
  } catch {
533
528
  const fallbackCpu = platform.os === "mac" ? ["-cpu", "max"] : ["-cpu", "qemu64"];
534
- process.stderr.write(" [vm] WARNING: HVF unavailable \u2014 using software emulation (VM will be slower)\n");
535
529
  await execa(platform.qemuBinary, buildArgs(fallbackCpu), { env: scrubEnv() });
536
530
  }
537
531
  return ports;
@@ -677,23 +671,32 @@ function addSshConfig(port) {
677
671
  fs4.writeFileSync(sshConfigPath, block, { mode: 384 });
678
672
  }
679
673
  }
680
- async function waitForSsh(port, timeoutMs = 9e5) {
674
+ async function waitForSsh(port, timeoutMs = 3e5) {
681
675
  const start = Date.now();
676
+ let attempt = 0;
677
+ const backoffMs = (n) => Math.min(3e3 * Math.pow(2, n), 3e4);
682
678
  while (Date.now() - start < timeoutMs) {
679
+ const ssh = new NodeSSH();
683
680
  try {
684
- const ssh = new NodeSSH();
685
681
  await ssh.connect({
686
682
  host: "localhost",
687
683
  port,
688
684
  username: "nexus",
689
685
  privateKeyPath: SSH_KEY,
690
- readyTimeout: 1e4,
686
+ readyTimeout: 5e3,
691
687
  hostVerifier: getHostVerifier()
692
688
  });
693
689
  ssh.dispose();
694
690
  return true;
695
691
  } catch {
696
- await new Promise((r) => setTimeout(r, 1e4));
692
+ try {
693
+ ssh.dispose();
694
+ } catch {
695
+ }
696
+ const delay = backoffMs(attempt++);
697
+ const remaining = timeoutMs - (Date.now() - start);
698
+ if (remaining <= 0) break;
699
+ await new Promise((r) => setTimeout(r, Math.min(delay, remaining)));
697
700
  }
698
701
  }
699
702
  return false;
@@ -743,7 +746,6 @@ var init_ssh = __esm({
743
746
 
744
747
  // src/cli.ts
745
748
  import { Command as Command14 } from "commander";
746
- import { createRequire as createRequire2 } from "module";
747
749
 
748
750
  // src/commands/init.ts
749
751
  init_banner();
@@ -783,6 +785,13 @@ var log = {
783
785
  },
784
786
  detail(label, value) {
785
787
  console.log(chalk3.dim(" " + label + ": ") + value);
788
+ },
789
+ progress(current, total, label) {
790
+ const pct = Math.round(current / total * 100);
791
+ const filled = Math.round(current / total * 20);
792
+ const bar = chalk3.cyan("\u2588".repeat(filled)) + chalk3.dim("\u2591".repeat(20 - filled));
793
+ process.stdout.write(`\r [${bar}] ${chalk3.bold(`${pct}%`)} ${chalk3.dim(label)}`);
794
+ if (current >= total) process.stdout.write("\n");
786
795
  }
787
796
  };
788
797
 
@@ -1024,7 +1033,12 @@ async function checkHealth(port, vmRunning) {
1024
1033
  sshReady: false,
1025
1034
  dockerReady: false,
1026
1035
  serverHealthy: false,
1027
- tunnelUrl: null
1036
+ tunnelUrl: null,
1037
+ dockerVersion: null,
1038
+ serverVersion: null,
1039
+ diskUsagePercent: null,
1040
+ uptimeSeconds: null,
1041
+ lastChecked: (/* @__PURE__ */ new Date()).toISOString()
1028
1042
  };
1029
1043
  if (!vmRunning) return status;
1030
1044
  try {
@@ -1034,13 +1048,33 @@ async function checkHealth(port, vmRunning) {
1034
1048
  return status;
1035
1049
  }
1036
1050
  try {
1037
- const { code } = await sshExec(port, "docker version --format '{{.Server.Version}}'");
1038
- status.dockerReady = code === 0;
1051
+ const { stdout, code } = await sshExec(port, "docker version --format '{{.Server.Version}}'");
1052
+ status.dockerReady = code === 0 && stdout.trim().length > 0;
1053
+ if (status.dockerReady) status.dockerVersion = stdout.trim();
1039
1054
  } catch {
1040
1055
  }
1041
1056
  try {
1042
1057
  const { stdout, code } = await sshExec(port, "curl -sf http://localhost:4200/health");
1043
1058
  status.serverHealthy = code === 0 && stdout.includes("ok");
1059
+ if (status.serverHealthy) {
1060
+ try {
1061
+ const parsed = JSON.parse(stdout);
1062
+ if (typeof parsed.version === "string") status.serverVersion = parsed.version;
1063
+ } catch {
1064
+ }
1065
+ }
1066
+ } catch {
1067
+ }
1068
+ try {
1069
+ const { stdout } = await sshExec(port, "df / --output=pcent | tail -1 | tr -dc '0-9'");
1070
+ const pct = parseInt(stdout.trim(), 10);
1071
+ if (!isNaN(pct)) status.diskUsagePercent = pct;
1072
+ } catch {
1073
+ }
1074
+ try {
1075
+ const { stdout } = await sshExec(port, "awk '{print int($1)}' /proc/uptime 2>/dev/null");
1076
+ const up = parseInt(stdout.trim(), 10);
1077
+ if (!isNaN(up)) status.uptimeSeconds = up;
1044
1078
  } catch {
1045
1079
  }
1046
1080
  try {
@@ -1055,6 +1089,8 @@ async function checkHealth(port, vmRunning) {
1055
1089
  async function waitForServer(port, timeoutMs = 9e5) {
1056
1090
  const start = Date.now();
1057
1091
  let lastLog = 0;
1092
+ let attempt = 0;
1093
+ const backoffMs = (n) => Math.min(3e3 * Math.pow(2, n), 3e4);
1058
1094
  while (Date.now() - start < timeoutMs) {
1059
1095
  try {
1060
1096
  const { stdout, code } = await sshExec(port, "curl -sf http://localhost:4200/health");
@@ -1072,13 +1108,18 @@ async function waitForServer(port, timeoutMs = 9e5) {
1072
1108
  } catch {
1073
1109
  }
1074
1110
  }
1075
- await new Promise((r) => setTimeout(r, 5e3));
1111
+ const delay = backoffMs(attempt++);
1112
+ const remaining = timeoutMs - (Date.now() - start);
1113
+ if (remaining <= 0) break;
1114
+ await new Promise((r) => setTimeout(r, Math.min(delay, remaining)));
1076
1115
  }
1077
1116
  return false;
1078
1117
  }
1079
1118
  async function waitForCloudInit(port, timeoutMs = 18e5) {
1080
1119
  const start = Date.now();
1081
1120
  let lastLog = 0;
1121
+ let attempt = 0;
1122
+ const backoffMs = (n) => Math.min(3e3 * Math.pow(2, n), 3e4);
1082
1123
  while (Date.now() - start < timeoutMs) {
1083
1124
  try {
1084
1125
  const { code } = await sshExec(port, "test -f /var/lib/cloud/instance/boot-finished");
@@ -1096,7 +1137,10 @@ async function waitForCloudInit(port, timeoutMs = 18e5) {
1096
1137
  } catch {
1097
1138
  }
1098
1139
  }
1099
- await new Promise((r) => setTimeout(r, 2e4));
1140
+ const delay = backoffMs(attempt++);
1141
+ const remaining = timeoutMs - (Date.now() - start);
1142
+ if (remaining <= 0) break;
1143
+ await new Promise((r) => setTimeout(r, Math.min(delay, remaining)));
1100
1144
  }
1101
1145
  return false;
1102
1146
  }
@@ -1122,12 +1166,14 @@ async function installCloudflared(sshPort, arch) {
1122
1166
  ].join(" && "));
1123
1167
  }
1124
1168
  async function startTunnel(sshPort) {
1125
- await sshExec(
1126
- sshPort,
1127
- "install -m 600 /dev/null /home/nexus/.nexus/tunnel.log && bash -c 'nohup cloudflared tunnel --no-autoupdate --url http://localhost:4200 > /home/nexus/.nexus/tunnel.log 2>&1 &'"
1128
- );
1169
+ await sshExec(sshPort, [
1170
+ "install -m 600 /dev/null /home/nexus/.nexus/tunnel.log",
1171
+ "&& nohup cloudflared tunnel --no-autoupdate --url http://localhost:4200",
1172
+ "> /home/nexus/.nexus/tunnel.log 2>&1 &",
1173
+ "disown"
1174
+ ].join(" "));
1129
1175
  const start = Date.now();
1130
- while (Date.now() - start < 12e4) {
1176
+ while (Date.now() - start < 6e4) {
1131
1177
  try {
1132
1178
  const { stdout } = await sshExec(sshPort, "grep -oE 'https://[a-z0-9-]+\\.trycloudflare\\.com' /home/nexus/.nexus/tunnel.log 2>/dev/null | head -1");
1133
1179
  if (stdout.includes("https://")) {
@@ -1160,18 +1206,11 @@ import crypto4 from "crypto";
1160
1206
  import { fileURLToPath } from "url";
1161
1207
  function getReleaseTarball() {
1162
1208
  const dir = path6.dirname(fileURLToPath(import.meta.url));
1163
- const possiblePaths = [
1164
- // Current directory (dev)
1165
- path6.join(dir, "nexus-release.tar.gz"),
1166
- // dist folder (when built)
1167
- path6.resolve(dir, "..", "dist", "nexus-release.tar.gz"),
1168
- // node_modules location (when installed from npm)
1169
- path6.resolve(dir, "..", "nexus-release.tar.gz")
1170
- ];
1171
- for (const tarballPath of possiblePaths) {
1172
- if (fs6.existsSync(tarballPath)) return tarballPath;
1173
- }
1174
- throw new Error("nexus-release.tar.gz not found. Run: npm install buildwithnexus@latest");
1209
+ const tarballPath = path6.join(dir, "nexus-release.tar.gz");
1210
+ if (fs6.existsSync(tarballPath)) return tarballPath;
1211
+ const rootPath = path6.resolve(dir, "..", "dist", "nexus-release.tar.gz");
1212
+ if (fs6.existsSync(rootPath)) return rootPath;
1213
+ throw new Error("nexus-release.tar.gz not found. Run: npm run bundle");
1175
1214
  }
1176
1215
  function getCloudInitTemplate() {
1177
1216
  const dir = path6.dirname(fileURLToPath(import.meta.url));
@@ -1250,11 +1289,6 @@ var phases = [
1250
1289
  const { config } = ctx;
1251
1290
  await withSpinner(spinner, "Generating SSH key...", async () => {
1252
1291
  await generateSshKey();
1253
- const pinFile = path6.join(path6.dirname(getKeyPath()), "vm_host_key.pin");
1254
- try {
1255
- fs6.unlinkSync(pinFile);
1256
- } catch {
1257
- }
1258
1292
  addSshConfig(config.sshPort);
1259
1293
  });
1260
1294
  }
@@ -1579,9 +1613,13 @@ var statusCommand = new Command4("status").description("Check NEXUS runtime heal
1579
1613
  console.log("");
1580
1614
  console.log(` ${check(health.vmRunning)} VM ${health.vmRunning ? chalk7.green("running") + chalk7.dim(` (PID ${getVmPid()})`) : chalk7.red("stopped")}`);
1581
1615
  console.log(` ${check(health.sshReady)} SSH ${health.sshReady ? chalk7.green("connected") + chalk7.dim(` (port ${config.sshPort})`) : chalk7.red("unreachable")}`);
1582
- console.log(` ${check(health.dockerReady)} Docker ${health.dockerReady ? chalk7.green("ready") : chalk7.red("not ready")}`);
1583
- console.log(` ${check(health.serverHealthy)} Server ${health.serverHealthy ? chalk7.green("healthy") + chalk7.dim(` (port ${config.httpPort})`) : chalk7.red("unhealthy")}`);
1616
+ console.log(` ${check(health.dockerReady)} Docker ${health.dockerReady ? chalk7.green("ready") + (health.dockerVersion ? chalk7.dim(` v${health.dockerVersion}`) : "") : chalk7.red("not ready")}`);
1617
+ console.log(` ${check(health.serverHealthy)} Server ${health.serverHealthy ? chalk7.green("healthy") + chalk7.dim(` (port ${config.httpPort})`) + (health.serverVersion ? chalk7.dim(` v${health.serverVersion}`) : "") : chalk7.red("unhealthy")}`);
1584
1618
  console.log(` ${check(!!health.tunnelUrl)} Tunnel ${health.tunnelUrl ? chalk7.green(health.tunnelUrl) : chalk7.dim("not active")}`);
1619
+ if (health.diskUsagePercent !== null) {
1620
+ const diskOk = health.diskUsagePercent < 85;
1621
+ console.log(` ${check(diskOk)} Disk ${diskOk ? chalk7.green(`${health.diskUsagePercent}% used`) : chalk7.yellow(`${health.diskUsagePercent}% used \u2014 consider cleanup`)}`);
1622
+ }
1585
1623
  console.log("");
1586
1624
  if (health.serverHealthy) {
1587
1625
  log.success(`NEXUS CLI ready \u2014 connect via: buildwithnexus ssh`);
@@ -1708,18 +1746,11 @@ init_qemu();
1708
1746
  init_ssh();
1709
1747
  function getReleaseTarball2() {
1710
1748
  const dir = path9.dirname(fileURLToPath2(import.meta.url));
1711
- const possiblePaths = [
1712
- // Current directory (dev)
1713
- path9.join(dir, "nexus-release.tar.gz"),
1714
- // dist folder (when built)
1715
- path9.resolve(dir, "..", "dist", "nexus-release.tar.gz"),
1716
- // node_modules location (when installed from npm)
1717
- path9.resolve(dir, "..", "nexus-release.tar.gz")
1718
- ];
1719
- for (const tarballPath of possiblePaths) {
1720
- if (fs8.existsSync(tarballPath)) return tarballPath;
1721
- }
1722
- throw new Error("nexus-release.tar.gz not found. Run: npm install buildwithnexus@latest");
1749
+ const tarballPath = path9.join(dir, "nexus-release.tar.gz");
1750
+ if (fs8.existsSync(tarballPath)) return tarballPath;
1751
+ const rootPath = path9.resolve(dir, "..", "dist", "nexus-release.tar.gz");
1752
+ if (fs8.existsSync(rootPath)) return rootPath;
1753
+ throw new Error("nexus-release.tar.gz not found. Reinstall buildwithnexus to get the latest release.");
1723
1754
  }
1724
1755
  var updateCommand = new Command7("update").description("Update NEXUS to the latest bundled release and restart").action(async () => {
1725
1756
  const config = loadConfig();
@@ -2420,9 +2451,18 @@ var EventStream = class {
2420
2451
  lastId = "0";
2421
2452
  onEvent;
2422
2453
  pollInterval = null;
2454
+ consecutiveErrors = 0;
2455
+ lastError = null;
2423
2456
  constructor(onEvent) {
2424
2457
  this.onEvent = onEvent;
2425
2458
  }
2459
+ getStatus() {
2460
+ return {
2461
+ active: this.active,
2462
+ consecutiveErrors: this.consecutiveErrors,
2463
+ lastError: this.lastError
2464
+ };
2465
+ }
2426
2466
  async start() {
2427
2467
  this.active = true;
2428
2468
  const config = loadConfig();
@@ -2435,13 +2475,23 @@ var EventStream = class {
2435
2475
  `curl -sf -H 'Last-Event-ID: ${this.lastId}' http://localhost:4200/events?timeout=1 2>/dev/null || true`
2436
2476
  );
2437
2477
  if (code === 0 && stdout.trim()) {
2478
+ this.consecutiveErrors = 0;
2479
+ this.lastError = null;
2438
2480
  const events = parseSSEData(stdout);
2439
2481
  for (const event of events) {
2440
2482
  if (event.id) this.lastId = event.id;
2441
2483
  this.onEvent(event);
2442
2484
  }
2443
2485
  }
2444
- } catch {
2486
+ } catch (err) {
2487
+ this.consecutiveErrors++;
2488
+ this.lastError = err instanceof Error ? err.message : String(err);
2489
+ if (this.consecutiveErrors >= 5) {
2490
+ this.onEvent({
2491
+ type: "error",
2492
+ content: `Event stream disconnected after ${this.consecutiveErrors} attempts: ${this.lastError}`
2493
+ });
2494
+ }
2445
2495
  }
2446
2496
  }, 2e3);
2447
2497
  }
@@ -2856,9 +2906,7 @@ var shellCommand2 = new Command13("shell").description("Launch the interactive N
2856
2906
  });
2857
2907
 
2858
2908
  // src/cli.ts
2859
- var _require2 = createRequire2(import.meta.url);
2860
- var { version: version2 } = _require2("../package.json");
2861
- var cli = new Command14().name("buildwithnexus").description("Auto-scaffold and launch a fully autonomous NEXUS runtime").version(version2);
2909
+ var cli = new Command14().name("buildwithnexus").description("Auto-scaffold and launch a fully autonomous NEXUS runtime").version("0.3.1");
2862
2910
  cli.addCommand(initCommand);
2863
2911
  cli.addCommand(startCommand);
2864
2912
  cli.addCommand(stopCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buildwithnexus",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "description": "Launch an autonomous AI runtime with triple-nested VM isolation in one command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,7 +19,7 @@
19
19
  "test": "vitest run",
20
20
  "test:watch": "vitest",
21
21
  "test:coverage": "vitest run --coverage",
22
- "prepublishOnly": "npm run build && NEXUS_SRC=/Users/garretteaglin/Projects/nexus npm run bundle"
22
+ "prepublishOnly": "npm run build"
23
23
  },
24
24
  "engines": {
25
25
  "node": ">=18"
Binary file