buildwithnexus 0.3.8 → 0.4.1

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
@@ -438,59 +438,59 @@ async function getPortBlocker(port) {
438
438
  return null;
439
439
  }
440
440
  }
441
+ async function findFreePort(preferred, max = 20) {
442
+ for (let offset = 0; offset < max; offset++) {
443
+ if (await isPortFree(preferred + offset)) return preferred + offset;
444
+ }
445
+ throw new Error(`No free port found near ${preferred}`);
446
+ }
441
447
  async function resolvePortConflicts(ports) {
442
448
  const labels = { ssh: "SSH", http: "HTTP", https: "HTTPS" };
443
449
  const resolved = { ...ports };
444
- const claimed = /* @__PURE__ */ new Set();
445
450
  for (const [key, port] of Object.entries(ports)) {
446
- if (await isPortFree(port) && !claimed.has(port)) {
447
- claimed.add(port);
448
- continue;
449
- }
450
- let altPort = null;
451
- for (let offset = 1; offset <= 50; offset++) {
452
- const candidate = port + offset;
453
- if (!claimed.has(candidate) && await isPortFree(candidate)) {
454
- altPort = candidate;
455
- break;
456
- }
451
+ if (await isPortFree(port)) continue;
452
+ const blocker = await getPortBlocker(port);
453
+ const desc = blocker ? `${blocker.process} (PID ${blocker.pid})` : "unknown process";
454
+ const altPort = await findFreePort(port + 1).catch(() => null);
455
+ const choices = [];
456
+ if (blocker) {
457
+ choices.push({
458
+ name: `Kill ${desc} and use port ${port}`,
459
+ value: "kill"
460
+ });
457
461
  }
458
462
  if (altPort) {
459
- resolved[key] = altPort;
460
- claimed.add(altPort);
461
- const blocker = await getPortBlocker(port);
462
- const desc = blocker ? `${blocker.process} (PID ${blocker.pid})` : "another process";
463
- console.log(chalk5.yellow(` \u26A0 Port ${port} (${labels[key]}) in use by ${desc} \u2192 using ${altPort}`));
464
- } else {
465
- const blocker = await getPortBlocker(port);
466
- const desc = blocker ? `${blocker.process} (PID ${blocker.pid})` : "unknown process";
467
- console.log("");
468
- console.log(chalk5.red(` \u2717 Port ${port} (${labels[key]}) is in use by ${desc} and no free port found nearby`));
469
- const action = await select({
470
- message: `No free ${labels[key]} port found. Kill ${desc} to free port ${port}?`,
471
- choices: [
472
- ...blocker ? [{ name: `Kill ${desc} and use port ${port}`, value: "kill" }] : [],
473
- { name: "Abort init", value: "abort" }
474
- ]
463
+ choices.push({
464
+ name: `Use alternate port ${altPort} instead`,
465
+ value: "alt"
475
466
  });
476
- if (action === "kill" && blocker) {
477
- try {
478
- process.kill(blocker.pid, "SIGTERM");
479
- await new Promise((r) => setTimeout(r, 1e3));
480
- if (!await isPortFree(port)) {
481
- process.kill(blocker.pid, "SIGKILL");
482
- await new Promise((r) => setTimeout(r, 500));
483
- }
484
- claimed.add(port);
485
- console.log(chalk5.green(` \u2713 Killed ${desc}, port ${port} is now free`));
486
- } catch {
487
- console.log(chalk5.red(` \u2717 Failed to kill PID ${blocker.pid}. Try: sudo kill ${blocker.pid}`));
488
- process.exit(1);
467
+ }
468
+ choices.push({ name: "Abort init", value: "abort" });
469
+ console.log("");
470
+ console.log(chalk5.yellow(` \u26A0 Port ${port} (${labels[key]}) is in use by ${desc}`));
471
+ const action = await select({
472
+ message: `How would you like to resolve the ${labels[key]} port conflict?`,
473
+ choices
474
+ });
475
+ if (action === "kill" && blocker) {
476
+ try {
477
+ process.kill(blocker.pid, "SIGTERM");
478
+ await new Promise((r) => setTimeout(r, 1e3));
479
+ if (!await isPortFree(port)) {
480
+ process.kill(blocker.pid, "SIGKILL");
481
+ await new Promise((r) => setTimeout(r, 500));
489
482
  }
490
- } else {
491
- console.log(chalk5.dim(" Init aborted."));
492
- process.exit(0);
483
+ console.log(chalk5.green(` \u2713 Killed ${desc}, port ${port} is now free`));
484
+ } catch {
485
+ console.log(chalk5.red(` \u2717 Failed to kill PID ${blocker.pid}. Try: sudo kill ${blocker.pid}`));
486
+ process.exit(1);
493
487
  }
488
+ } else if (action === "alt" && altPort) {
489
+ resolved[key] = altPort;
490
+ console.log(chalk5.green(` \u2713 Using port ${altPort} for ${labels[key]}`));
491
+ } else {
492
+ console.log(chalk5.dim(" Init aborted."));
493
+ process.exit(0);
494
494
  }
495
495
  }
496
496
  return resolved;
@@ -671,7 +671,7 @@ function addSshConfig(port) {
671
671
  fs4.writeFileSync(sshConfigPath, block, { mode: 384 });
672
672
  }
673
673
  }
674
- async function waitForSsh(port, timeoutMs = 6e5) {
674
+ async function waitForSsh(port, timeoutMs = 3e5) {
675
675
  const start = Date.now();
676
676
  while (Date.now() - start < timeoutMs) {
677
677
  try {
@@ -737,7 +737,6 @@ var init_ssh = __esm({
737
737
 
738
738
  // src/cli.ts
739
739
  import { Command as Command14 } from "commander";
740
- import { createRequire } from "module";
741
740
 
742
741
  // src/commands/init.ts
743
742
  init_banner();
@@ -939,13 +938,13 @@ async function renderCloudInit(data, templateContent) {
939
938
  }
940
939
  const safeData = { ...data, sshPubKey: yamlEscape(trimmedPubKey), keys: safeKeys };
941
940
  const rendered = ejs.render(templateContent, safeData);
942
- const outputPath = path5.join(CONFIGS_DIR, "user-data");
941
+ const outputPath = path5.join(CONFIGS_DIR, "user-data.yaml");
943
942
  fs5.writeFileSync(outputPath, rendered, { mode: 384 });
944
- audit("cloudinit_rendered", "user-data written");
943
+ audit("cloudinit_rendered", "user-data.yaml written");
945
944
  return outputPath;
946
945
  }
947
946
  async function createCloudInitIso(userDataPath) {
948
- const metaDataPath = path5.join(CONFIGS_DIR, "meta-data");
947
+ const metaDataPath = path5.join(CONFIGS_DIR, "meta-data.yaml");
949
948
  fs5.writeFileSync(metaDataPath, "instance-id: nexus-vm-1\nlocal-hostname: nexus-vm\n", { mode: 384 });
950
949
  const isoPath = path5.join(IMAGES_DIR2, "init.iso");
951
950
  const env = scrubEnv();
@@ -1006,7 +1005,7 @@ async function createCloudInitIso(userDataPath) {
1006
1005
  fs5.unlinkSync(metaDataPath);
1007
1006
  } catch {
1008
1007
  }
1009
- audit("cloudinit_plaintext_deleted", "user-data and meta-data removed");
1008
+ audit("cloudinit_plaintext_deleted", "user-data.yaml and meta-data.yaml removed");
1010
1009
  }
1011
1010
  }
1012
1011
 
@@ -1046,7 +1045,7 @@ async function checkHealth(port, vmRunning) {
1046
1045
  }
1047
1046
  return status;
1048
1047
  }
1049
- async function waitForServer(port, timeoutMs = 36e5) {
1048
+ async function waitForServer(port, timeoutMs = 9e5) {
1050
1049
  const start = Date.now();
1051
1050
  let lastLog = 0;
1052
1051
  while (Date.now() - start < timeoutMs) {
@@ -1070,7 +1069,7 @@ async function waitForServer(port, timeoutMs = 36e5) {
1070
1069
  }
1071
1070
  return false;
1072
1071
  }
1073
- async function waitForCloudInit(port, timeoutMs = 72e5) {
1072
+ async function waitForCloudInit(port, timeoutMs = 18e5) {
1074
1073
  const start = Date.now();
1075
1074
  let lastLog = 0;
1076
1075
  while (Date.now() - start < timeoutMs) {
@@ -1306,8 +1305,6 @@ var phases = [
1306
1305
  name: "VM Provisioning",
1307
1306
  run: async (ctx, spinner) => {
1308
1307
  const { config, keys, tarballPath } = ctx;
1309
- const pinPath = path6.join(process.env.HOME || "~", ".buildwithnexus", "ssh", "vm_host_key.pin");
1310
- if (fs6.existsSync(pinPath)) fs6.unlinkSync(pinPath);
1311
1308
  spinner.text = "Waiting for SSH...";
1312
1309
  spinner.start();
1313
1310
  const sshReady = await waitForSsh(config.sshPort);
@@ -1340,7 +1337,7 @@ var phases = [
1340
1337
  spinner.start();
1341
1338
  const cloudInitDone = await waitForCloudInit(config.sshPort);
1342
1339
  if (!cloudInitDone) {
1343
- fail(spinner, "Cloud-init timed out after 2 hours");
1340
+ fail(spinner, "Cloud-init timed out after 30 minutes");
1344
1341
  log.warn("Check progress: buildwithnexus ssh \u2192 tail -f /var/log/cloud-init-output.log");
1345
1342
  throw new Error("Cloud-init timed out");
1346
1343
  }
@@ -2835,9 +2832,7 @@ var shellCommand2 = new Command13("shell").description("Launch the interactive N
2835
2832
  });
2836
2833
 
2837
2834
  // src/cli.ts
2838
- var require2 = createRequire(import.meta.url);
2839
- var { version } = require2("../package.json");
2840
- var cli = new Command14().name("buildwithnexus").description("Auto-scaffold and launch a fully autonomous NEXUS runtime").version(version);
2835
+ var cli = new Command14().name("buildwithnexus").description("Auto-scaffold and launch a fully autonomous NEXUS runtime").version("0.3.1");
2841
2836
  cli.addCommand(initCommand);
2842
2837
  cli.addCommand(startCommand);
2843
2838
  cli.addCommand(stopCommand);
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buildwithnexus",
3
- "version": "0.3.8",
3
+ "version": "0.4.1",
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 && npm run bundle"
22
+ "prepublishOnly": "npm run build && NEXUS_SRC=/Users/garretteaglin/Projects/nexus npm run bundle"
23
23
  },
24
24
  "engines": {
25
25
  "node": ">=18"