buildwithnexus 0.5.2 → 0.5.4

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
@@ -20,7 +20,7 @@ __export(banner_exports, {
20
20
  import chalk from "chalk";
21
21
  function showBanner() {
22
22
  console.log(BANNER);
23
- console.log(chalk.dim(" v0.5.1 \xB7 buildwithnexus.dev\n"));
23
+ console.log(chalk.dim(" v0.5.4 \xB7 buildwithnexus.dev\n"));
24
24
  }
25
25
  function showPhase(phase, total, description) {
26
26
  const progress = chalk.cyan(`[${phase}/${total}]`);
@@ -282,26 +282,26 @@ __export(secrets_exports, {
282
282
  saveConfig: () => saveConfig,
283
283
  saveKeys: () => saveKeys
284
284
  });
285
- import fs2 from "fs";
286
- import path2 from "path";
285
+ import fs3 from "fs";
286
+ import path3 from "path";
287
287
  import crypto2 from "crypto";
288
288
  function ensureHome() {
289
- fs2.mkdirSync(NEXUS_HOME2, { recursive: true, mode: 448 });
290
- fs2.mkdirSync(path2.join(NEXUS_HOME2, "vm", "images"), { recursive: true, mode: 448 });
291
- fs2.mkdirSync(path2.join(NEXUS_HOME2, "vm", "configs"), { recursive: true, mode: 448 });
292
- fs2.mkdirSync(path2.join(NEXUS_HOME2, "vm", "logs"), { recursive: true, mode: 448 });
293
- fs2.mkdirSync(path2.join(NEXUS_HOME2, "ssh"), { recursive: true, mode: 448 });
289
+ fs3.mkdirSync(NEXUS_HOME2, { recursive: true, mode: 448 });
290
+ fs3.mkdirSync(path3.join(NEXUS_HOME2, "vm", "images"), { recursive: true, mode: 448 });
291
+ fs3.mkdirSync(path3.join(NEXUS_HOME2, "vm", "configs"), { recursive: true, mode: 448 });
292
+ fs3.mkdirSync(path3.join(NEXUS_HOME2, "vm", "logs"), { recursive: true, mode: 448 });
293
+ fs3.mkdirSync(path3.join(NEXUS_HOME2, "ssh"), { recursive: true, mode: 448 });
294
294
  }
295
295
  function generateMasterSecret() {
296
296
  return crypto2.randomBytes(32).toString("base64url");
297
297
  }
298
298
  function saveConfig(config) {
299
299
  const { masterSecret: _secret, ...safeConfig } = config;
300
- fs2.writeFileSync(CONFIG_PATH, JSON.stringify(safeConfig, null, 2), { mode: 384 });
300
+ fs3.writeFileSync(CONFIG_PATH, JSON.stringify(safeConfig, null, 2), { mode: 384 });
301
301
  }
302
302
  function loadConfig() {
303
- if (!fs2.existsSync(CONFIG_PATH)) return null;
304
- return JSON.parse(fs2.readFileSync(CONFIG_PATH, "utf-8"));
303
+ if (!fs3.existsSync(CONFIG_PATH)) return null;
304
+ return JSON.parse(fs3.readFileSync(CONFIG_PATH, "utf-8"));
305
305
  }
306
306
  function saveKeys(keys) {
307
307
  const violations = validateAllKeys(keys);
@@ -309,13 +309,13 @@ function saveKeys(keys) {
309
309
  throw new DlpViolation(`Key validation failed: ${violations.join("; ")}`);
310
310
  }
311
311
  const lines = Object.entries(keys).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`);
312
- fs2.writeFileSync(KEYS_PATH, lines.join("\n") + "\n", { mode: 384 });
312
+ fs3.writeFileSync(KEYS_PATH, lines.join("\n") + "\n", { mode: 384 });
313
313
  sealKeysFile(KEYS_PATH, keys.NEXUS_MASTER_SECRET);
314
314
  audit("keys_saved", `${Object.keys(keys).filter((k) => keys[k]).length} keys saved`);
315
315
  }
316
316
  function loadKeys() {
317
- if (!fs2.existsSync(KEYS_PATH)) return null;
318
- const content = fs2.readFileSync(KEYS_PATH, "utf-8");
317
+ if (!fs3.existsSync(KEYS_PATH)) return null;
318
+ const content = fs3.readFileSync(KEYS_PATH, "utf-8");
319
319
  const keys = {};
320
320
  for (const line of content.split("\n")) {
321
321
  const eq = line.indexOf("=");
@@ -341,9 +341,9 @@ var init_secrets = __esm({
341
341
  "src/core/secrets.ts"() {
342
342
  "use strict";
343
343
  init_dlp();
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");
344
+ NEXUS_HOME2 = process.env.NEXUS_HOME || path3.join(process.env.HOME || "~", ".buildwithnexus");
345
+ CONFIG_PATH = process.env.NEXUS_CONFIG_PATH || path3.join(NEXUS_HOME2, "config.json");
346
+ KEYS_PATH = process.env.NEXUS_KEYS_PATH || path3.join(NEXUS_HOME2, ".env.keys");
347
347
  }
348
348
  });
349
349
 
@@ -360,9 +360,9 @@ __export(qemu_exports, {
360
360
  resolvePortConflicts: () => resolvePortConflicts,
361
361
  stopVm: () => stopVm
362
362
  });
363
- import fs3 from "fs";
363
+ import fs4 from "fs";
364
364
  import net from "net";
365
- import path3 from "path";
365
+ import path4 from "path";
366
366
  import { execa, execaSync } from "execa";
367
367
  import { select } from "@inquirer/prompts";
368
368
  import chalk5 from "chalk";
@@ -395,15 +395,15 @@ async function installQemu(platform) {
395
395
  }
396
396
  }
397
397
  async function downloadImage(platform) {
398
- const imagePath = path3.join(IMAGES_DIR, platform.ubuntuImage);
399
- if (fs3.existsSync(imagePath)) return imagePath;
398
+ const imagePath = path4.join(IMAGES_DIR, platform.ubuntuImage);
399
+ if (fs4.existsSync(imagePath)) return imagePath;
400
400
  const url = `${UBUNTU_BASE_URL}/${platform.ubuntuImage}`;
401
401
  await execa("curl", ["-L", "-C", "-", "-o", imagePath, "--progress-bar", url], { stdio: "inherit", env: scrubEnv() });
402
402
  return imagePath;
403
403
  }
404
404
  async function createDisk(basePath, sizeGb) {
405
- const diskPath = path3.join(IMAGES_DIR, "nexus-vm-disk.qcow2");
406
- if (fs3.existsSync(diskPath)) return diskPath;
405
+ const diskPath = path4.join(IMAGES_DIR, "nexus-vm-disk.qcow2");
406
+ if (fs4.existsSync(diskPath)) return diskPath;
407
407
  await execa("qemu-img", ["create", "-f", "qcow2", "-b", basePath, "-F", "qcow2", diskPath, `${sizeGb}G`], { env: scrubEnv() });
408
408
  return diskPath;
409
409
  }
@@ -497,7 +497,7 @@ async function resolvePortConflicts(ports) {
497
497
  }
498
498
  async function launchVm(platform, diskPath, initIsoPath, ram, cpus, ports) {
499
499
  const machineArgs = platform.os === "mac" ? ["-machine", "virt,gic-version=3"] : ["-machine", "pc"];
500
- const biosArgs = fs3.existsSync(platform.biosPath) ? ["-bios", platform.biosPath] : [];
500
+ const biosArgs = fs4.existsSync(platform.biosPath) ? ["-bios", platform.biosPath] : [];
501
501
  const buildArgs = (cpuArgs) => [
502
502
  ...machineArgs,
503
503
  ...cpuArgs,
@@ -531,8 +531,8 @@ async function launchVm(platform, diskPath, initIsoPath, ram, cpus, ports) {
531
531
  return ports;
532
532
  }
533
533
  function readValidPid() {
534
- if (!fs3.existsSync(PID_FILE)) return null;
535
- const raw = fs3.readFileSync(PID_FILE, "utf-8").trim();
534
+ if (!fs4.existsSync(PID_FILE)) return null;
535
+ const raw = fs4.readFileSync(PID_FILE, "utf-8").trim();
536
536
  const pid = parseInt(raw, 10);
537
537
  if (!Number.isInteger(pid) || pid <= 1 || pid > 4194304) return null;
538
538
  return pid;
@@ -560,7 +560,7 @@ function stopVm() {
560
560
  if (!pid) return;
561
561
  if (!isQemuPid(pid)) {
562
562
  try {
563
- fs3.unlinkSync(PID_FILE);
563
+ fs4.unlinkSync(PID_FILE);
564
564
  } catch {
565
565
  }
566
566
  return;
@@ -570,7 +570,7 @@ function stopVm() {
570
570
  } catch {
571
571
  }
572
572
  try {
573
- fs3.unlinkSync(PID_FILE);
573
+ fs4.unlinkSync(PID_FILE);
574
574
  } catch {
575
575
  }
576
576
  }
@@ -583,9 +583,9 @@ var init_qemu = __esm({
583
583
  "use strict";
584
584
  init_secrets();
585
585
  init_dlp();
586
- VM_DIR = path3.join(NEXUS_HOME2, "vm");
587
- IMAGES_DIR = path3.join(VM_DIR, "images");
588
- PID_FILE = path3.join(VM_DIR, "qemu.pid");
586
+ VM_DIR = path4.join(NEXUS_HOME2, "vm");
587
+ IMAGES_DIR = path4.join(VM_DIR, "images");
588
+ PID_FILE = path4.join(VM_DIR, "qemu.pid");
589
589
  UBUNTU_BASE_URL = "https://cloud-images.ubuntu.com/jammy/current";
590
590
  }
591
591
  });
@@ -602,21 +602,21 @@ __export(ssh_exports, {
602
602
  sshUploadFile: () => sshUploadFile,
603
603
  waitForSsh: () => waitForSsh
604
604
  });
605
- import fs4 from "fs";
606
- import path4 from "path";
605
+ import fs5 from "fs";
606
+ import path5 from "path";
607
607
  import crypto3 from "crypto";
608
608
  import { execa as execa2 } from "execa";
609
609
  import { NodeSSH } from "node-ssh";
610
610
  function getHostVerifier() {
611
- if (!fs4.existsSync(PINNED_HOST_KEY)) {
611
+ if (!fs5.existsSync(PINNED_HOST_KEY)) {
612
612
  return (key) => {
613
613
  const fp = crypto3.createHash("sha256").update(key).digest("base64");
614
- fs4.writeFileSync(PINNED_HOST_KEY, fp, { mode: 384 });
614
+ fs5.writeFileSync(PINNED_HOST_KEY, fp, { mode: 384 });
615
615
  audit("ssh_exec", `host key pinned: SHA256:${fp}`);
616
616
  return true;
617
617
  };
618
618
  }
619
- const pinned = fs4.readFileSync(PINNED_HOST_KEY, "utf-8").trim();
619
+ const pinned = fs5.readFileSync(PINNED_HOST_KEY, "utf-8").trim();
620
620
  return (key) => {
621
621
  const fp = crypto3.createHash("sha256").update(key).digest("base64");
622
622
  const match = fp === pinned;
@@ -628,11 +628,11 @@ function getKeyPath() {
628
628
  return SSH_KEY;
629
629
  }
630
630
  function getPubKey() {
631
- return fs4.readFileSync(SSH_PUB_KEY, "utf-8").trim();
631
+ return fs5.readFileSync(SSH_PUB_KEY, "utf-8").trim();
632
632
  }
633
633
  async function generateSshKey() {
634
- if (fs4.existsSync(SSH_KEY)) return;
635
- fs4.mkdirSync(SSH_DIR, { recursive: true });
634
+ if (fs5.existsSync(SSH_KEY)) return;
635
+ fs5.mkdirSync(SSH_DIR, { recursive: true });
636
636
  await execa2("ssh-keygen", [
637
637
  "-t",
638
638
  "ed25519",
@@ -644,13 +644,13 @@ async function generateSshKey() {
644
644
  "buildwithnexus@localhost",
645
645
  "-q"
646
646
  ], { env: scrubEnv() });
647
- fs4.chmodSync(SSH_KEY, 384);
648
- fs4.chmodSync(SSH_PUB_KEY, 420);
647
+ fs5.chmodSync(SSH_KEY, 384);
648
+ fs5.chmodSync(SSH_PUB_KEY, 420);
649
649
  }
650
650
  function addSshConfig(port) {
651
- const sshConfigPath = path4.join(process.env.HOME || "~", ".ssh", "config");
652
- const sshDir = path4.dirname(sshConfigPath);
653
- fs4.mkdirSync(sshDir, { recursive: true });
651
+ const sshConfigPath = path5.join(process.env.HOME || "~", ".ssh", "config");
652
+ const sshDir = path5.dirname(sshConfigPath);
653
+ fs5.mkdirSync(sshDir, { recursive: true });
654
654
  const block = [
655
655
  "",
656
656
  "Host nexus-vm",
@@ -663,12 +663,12 @@ function addSshConfig(port) {
663
663
  " ServerAliveInterval 60",
664
664
  ""
665
665
  ].join("\n");
666
- if (fs4.existsSync(sshConfigPath)) {
667
- const existing = fs4.readFileSync(sshConfigPath, "utf-8");
666
+ if (fs5.existsSync(sshConfigPath)) {
667
+ const existing = fs5.readFileSync(sshConfigPath, "utf-8");
668
668
  if (existing.includes("Host nexus-vm")) return;
669
- fs4.appendFileSync(sshConfigPath, block);
669
+ fs5.appendFileSync(sshConfigPath, block);
670
670
  } else {
671
- fs4.writeFileSync(sshConfigPath, block, { mode: 384 });
671
+ fs5.writeFileSync(sshConfigPath, block, { mode: 384 });
672
672
  }
673
673
  }
674
674
  async function waitForSsh(port, timeoutMs = 3e5) {
@@ -736,11 +736,11 @@ var init_ssh = __esm({
736
736
  "use strict";
737
737
  init_secrets();
738
738
  init_dlp();
739
- SSH_DIR = path4.join(NEXUS_HOME2, "ssh");
740
- SSH_KEY = path4.join(SSH_DIR, "id_nexus_vm");
741
- SSH_PUB_KEY = path4.join(SSH_DIR, "id_nexus_vm.pub");
742
- KNOWN_HOSTS = path4.join(SSH_DIR, "known_hosts_nexus_vm");
743
- PINNED_HOST_KEY = path4.join(SSH_DIR, "vm_host_key.pin");
739
+ SSH_DIR = path5.join(NEXUS_HOME2, "ssh");
740
+ SSH_KEY = path5.join(SSH_DIR, "id_nexus_vm");
741
+ SSH_PUB_KEY = path5.join(SSH_DIR, "id_nexus_vm.pub");
742
+ KNOWN_HOSTS = path5.join(SSH_DIR, "known_hosts_nexus_vm");
743
+ PINNED_HOST_KEY = path5.join(SSH_DIR, "vm_host_key.pin");
744
744
  }
745
745
  });
746
746
 
@@ -799,6 +799,39 @@ var log = {
799
799
  init_dlp();
800
800
  import { input, confirm, password } from "@inquirer/prompts";
801
801
  import chalk4 from "chalk";
802
+ import fs2 from "fs";
803
+ import path2 from "path";
804
+ import os from "os";
805
+ function loadInitConfigFromDisk() {
806
+ const homeDir = os.homedir();
807
+ const configPath = path2.join(homeDir, ".buildwithnexus", "config.json");
808
+ const keysPath = path2.join(homeDir, ".buildwithnexus", ".env.keys");
809
+ if (!fs2.existsSync(configPath)) return null;
810
+ try {
811
+ const configData = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
812
+ const keysData = {};
813
+ if (fs2.existsSync(keysPath)) {
814
+ const keysContent = fs2.readFileSync(keysPath, "utf-8");
815
+ keysContent.split("\n").forEach((line) => {
816
+ const [key, ...valueParts] = line.split("=");
817
+ if (key && valueParts.length > 0) {
818
+ keysData[key] = valueParts.join("=");
819
+ }
820
+ });
821
+ }
822
+ return {
823
+ anthropicKey: keysData["ANTHROPIC_API_KEY"] || "",
824
+ openaiKey: keysData["OPENAI_API_KEY"] || "",
825
+ googleKey: keysData["GOOGLE_API_KEY"] || "",
826
+ vmRam: configData.vmRam || 4,
827
+ vmCpus: configData.vmCpus || 2,
828
+ vmDisk: configData.vmDisk || 10,
829
+ enableTunnel: configData.enableTunnel !== false
830
+ };
831
+ } catch {
832
+ return null;
833
+ }
834
+ }
802
835
  async function promptInitConfig() {
803
836
  console.log(chalk4.bold("\n API Keys\n"));
804
837
  const anthropicKey = await password({
@@ -892,10 +925,10 @@ async function promptInitConfig() {
892
925
  }
893
926
 
894
927
  // src/core/platform.ts
895
- import os from "os";
928
+ import os2 from "os";
896
929
  function detectPlatform() {
897
- const platform = os.platform();
898
- const arch = os.arch();
930
+ const platform = os2.platform();
931
+ const arch = os2.arch();
899
932
  if (platform === "darwin") {
900
933
  return {
901
934
  os: "mac",
@@ -937,12 +970,12 @@ init_ssh();
937
970
  // src/core/cloudinit.ts
938
971
  init_secrets();
939
972
  init_dlp();
940
- import fs5 from "fs";
941
- import path5 from "path";
973
+ import fs6 from "fs";
974
+ import path6 from "path";
942
975
  import ejs from "ejs";
943
976
  import { execa as execa3 } from "execa";
944
- var CONFIGS_DIR = path5.join(NEXUS_HOME2, "vm", "configs");
945
- var IMAGES_DIR2 = path5.join(NEXUS_HOME2, "vm", "images");
977
+ var CONFIGS_DIR = path6.join(NEXUS_HOME2, "vm", "configs");
978
+ var IMAGES_DIR2 = path6.join(NEXUS_HOME2, "vm", "images");
946
979
  async function renderCloudInit(data, templateContent) {
947
980
  const trimmedPubKey = data.sshPubKey.trim();
948
981
  if (!/^(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp\d+) [A-Za-z0-9+/=]+ ?\S*$/.test(trimmedPubKey)) {
@@ -954,15 +987,15 @@ async function renderCloudInit(data, templateContent) {
954
987
  }
955
988
  const safeData = { ...data, sshPubKey: yamlEscape(trimmedPubKey), keys: safeKeys };
956
989
  const rendered = ejs.render(templateContent, safeData);
957
- const outputPath = path5.join(CONFIGS_DIR, "user-data.yaml");
958
- fs5.writeFileSync(outputPath, rendered, { mode: 384 });
990
+ const outputPath = path6.join(CONFIGS_DIR, "user-data.yaml");
991
+ fs6.writeFileSync(outputPath, rendered, { mode: 384 });
959
992
  audit("cloudinit_rendered", "user-data.yaml written");
960
993
  return outputPath;
961
994
  }
962
995
  async function createCloudInitIso(userDataPath) {
963
- const metaDataPath = path5.join(CONFIGS_DIR, "meta-data.yaml");
964
- fs5.writeFileSync(metaDataPath, "instance-id: nexus-vm-1\nlocal-hostname: nexus-vm\n", { mode: 384 });
965
- const isoPath = path5.join(IMAGES_DIR2, "init.iso");
996
+ const metaDataPath = path6.join(CONFIGS_DIR, "meta-data.yaml");
997
+ fs6.writeFileSync(metaDataPath, "instance-id: nexus-vm-1\nlocal-hostname: nexus-vm\n", { mode: 384 });
998
+ const isoPath = path6.join(IMAGES_DIR2, "init.iso");
966
999
  const env = scrubEnv();
967
1000
  try {
968
1001
  let created = false;
@@ -985,10 +1018,10 @@ async function createCloudInitIso(userDataPath) {
985
1018
  }
986
1019
  if (!created) {
987
1020
  try {
988
- const stagingDir = path5.join(CONFIGS_DIR, "cidata-staging");
989
- fs5.mkdirSync(stagingDir, { recursive: true, mode: 448 });
990
- fs5.copyFileSync(userDataPath, path5.join(stagingDir, "user-data"));
991
- fs5.copyFileSync(metaDataPath, path5.join(stagingDir, "meta-data"));
1021
+ const stagingDir = path6.join(CONFIGS_DIR, "cidata-staging");
1022
+ fs6.mkdirSync(stagingDir, { recursive: true, mode: 448 });
1023
+ fs6.copyFileSync(userDataPath, path6.join(stagingDir, "user-data"));
1024
+ fs6.copyFileSync(metaDataPath, path6.join(stagingDir, "meta-data"));
992
1025
  await execa3("hdiutil", [
993
1026
  "makehybrid",
994
1027
  "-o",
@@ -999,7 +1032,7 @@ async function createCloudInitIso(userDataPath) {
999
1032
  "cidata",
1000
1033
  stagingDir
1001
1034
  ], { env });
1002
- fs5.rmSync(stagingDir, { recursive: true, force: true });
1035
+ fs6.rmSync(stagingDir, { recursive: true, force: true });
1003
1036
  created = true;
1004
1037
  } catch {
1005
1038
  }
@@ -1009,16 +1042,16 @@ async function createCloudInitIso(userDataPath) {
1009
1042
  "Cannot create cloud-init ISO: none of mkisofs, genisoimage, or hdiutil are available. On macOS, install cdrtools: brew install cdrtools. On Linux: sudo apt install genisoimage"
1010
1043
  );
1011
1044
  }
1012
- fs5.chmodSync(isoPath, 384);
1045
+ fs6.chmodSync(isoPath, 384);
1013
1046
  audit("cloudinit_iso_created", "init.iso created");
1014
1047
  return isoPath;
1015
1048
  } finally {
1016
1049
  try {
1017
- fs5.unlinkSync(userDataPath);
1050
+ fs6.unlinkSync(userDataPath);
1018
1051
  } catch {
1019
1052
  }
1020
1053
  try {
1021
- fs5.unlinkSync(metaDataPath);
1054
+ fs6.unlinkSync(metaDataPath);
1022
1055
  } catch {
1023
1056
  }
1024
1057
  audit("cloudinit_plaintext_deleted", "user-data.yaml and meta-data.yaml removed");
@@ -1199,25 +1232,25 @@ async function stopTunnel(sshPort) {
1199
1232
  // src/commands/init.ts
1200
1233
  init_dlp();
1201
1234
  init_ssh();
1202
- import fs6 from "fs";
1203
- import path6 from "path";
1204
- import os2 from "os";
1235
+ import fs7 from "fs";
1236
+ import path7 from "path";
1237
+ import os3 from "os";
1205
1238
  import crypto4 from "crypto";
1206
1239
  import { fileURLToPath } from "url";
1207
1240
  function getReleaseTarball() {
1208
- const dir = path6.dirname(fileURLToPath(import.meta.url));
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;
1241
+ const dir = path7.dirname(fileURLToPath(import.meta.url));
1242
+ const tarballPath = path7.join(dir, "nexus-release.tar.gz");
1243
+ if (fs7.existsSync(tarballPath)) return tarballPath;
1244
+ const rootPath = path7.resolve(dir, "..", "dist", "nexus-release.tar.gz");
1245
+ if (fs7.existsSync(rootPath)) return rootPath;
1213
1246
  throw new Error("nexus-release.tar.gz not found. Run: npm run bundle");
1214
1247
  }
1215
1248
  function getCloudInitTemplate() {
1216
- const dir = path6.dirname(fileURLToPath(import.meta.url));
1217
- const templatePath = path6.join(dir, "templates", "cloud-init.yaml.ejs");
1218
- if (fs6.existsSync(templatePath)) return fs6.readFileSync(templatePath, "utf-8");
1219
- const srcPath = path6.resolve(dir, "..", "src", "templates", "cloud-init.yaml.ejs");
1220
- return fs6.readFileSync(srcPath, "utf-8");
1249
+ const dir = path7.dirname(fileURLToPath(import.meta.url));
1250
+ const templatePath = path7.join(dir, "templates", "cloud-init.yaml.ejs");
1251
+ if (fs7.existsSync(templatePath)) return fs7.readFileSync(templatePath, "utf-8");
1252
+ const srcPath = path7.resolve(dir, "..", "src", "templates", "cloud-init.yaml.ejs");
1253
+ return fs7.readFileSync(srcPath, "utf-8");
1221
1254
  }
1222
1255
  async function withSpinner(spinner, label, fn) {
1223
1256
  spinner.text = label;
@@ -1235,7 +1268,11 @@ var phases = [
1235
1268
  const platform = detectPlatform();
1236
1269
  log.detail("Platform", `${platform.os} ${platform.arch}`);
1237
1270
  log.detail("QEMU", platform.qemuBinary);
1238
- const userConfig = await promptInitConfig();
1271
+ const isNonInteractive = process.env.BUILDWITHNEXUS_NONINTERACTIVE === "1" || process.env.CI === "true";
1272
+ let userConfig = isNonInteractive ? loadInitConfigFromDisk() : null;
1273
+ if (!userConfig) {
1274
+ userConfig = await promptInitConfig();
1275
+ }
1239
1276
  ensureHome();
1240
1277
  const masterSecret = generateMasterSecret();
1241
1278
  const config = {
@@ -1371,15 +1408,15 @@ var phases = [
1371
1408
  );
1372
1409
  await withSpinner(spinner, "Staging API keys...", async () => {
1373
1410
  const keysContent = Object.entries(keys).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
1374
- const tmpKeysPath = path6.join(os2.tmpdir(), `.nexus-keys-${crypto4.randomBytes(8).toString("hex")}`);
1375
- fs6.writeFileSync(tmpKeysPath, keysContent, { mode: 384 });
1411
+ const tmpKeysPath = path7.join(os3.tmpdir(), `.nexus-keys-${crypto4.randomBytes(8).toString("hex")}`);
1412
+ fs7.writeFileSync(tmpKeysPath, keysContent, { mode: 384 });
1376
1413
  try {
1377
1414
  await sshUploadFile(config.sshPort, tmpKeysPath, "/tmp/.nexus-env-keys");
1378
1415
  await sshExec(config.sshPort, "chmod 600 /tmp/.nexus-env-keys");
1379
1416
  } finally {
1380
1417
  try {
1381
- fs6.writeFileSync(tmpKeysPath, "0".repeat(keysContent.length));
1382
- fs6.unlinkSync(tmpKeysPath);
1418
+ fs7.writeFileSync(tmpKeysPath, "0".repeat(keysContent.length));
1419
+ fs7.unlinkSync(tmpKeysPath);
1383
1420
  } catch {
1384
1421
  }
1385
1422
  }
@@ -1500,7 +1537,7 @@ init_secrets();
1500
1537
  init_qemu();
1501
1538
  init_ssh();
1502
1539
  init_secrets();
1503
- import path7 from "path";
1540
+ import path8 from "path";
1504
1541
  var startCommand = new Command2("start").description("Start the NEXUS runtime").action(async () => {
1505
1542
  const config = loadConfig();
1506
1543
  if (!config) {
@@ -1512,8 +1549,8 @@ var startCommand = new Command2("start").description("Start the NEXUS runtime").
1512
1549
  return;
1513
1550
  }
1514
1551
  const platform = detectPlatform();
1515
- const diskPath = path7.join(NEXUS_HOME2, "vm", "images", "nexus-vm-disk.qcow2");
1516
- const isoPath = path7.join(NEXUS_HOME2, "vm", "images", "init.iso");
1552
+ const diskPath = path8.join(NEXUS_HOME2, "vm", "images", "nexus-vm-disk.qcow2");
1553
+ const isoPath = path8.join(NEXUS_HOME2, "vm", "images", "init.iso");
1517
1554
  let spinner = createSpinner("Starting VM...");
1518
1555
  spinner.start();
1519
1556
  await launchVm(platform, diskPath, isoPath, config.vmRam, config.vmCpus, {
@@ -1629,10 +1666,10 @@ var statusCommand = new Command4("status").description("Check NEXUS runtime heal
1629
1666
  // src/commands/doctor.ts
1630
1667
  import { Command as Command5 } from "commander";
1631
1668
  import chalk8 from "chalk";
1632
- import fs7 from "fs";
1669
+ import fs8 from "fs";
1633
1670
  init_qemu();
1634
1671
  init_secrets();
1635
- import path8 from "path";
1672
+ import path9 from "path";
1636
1673
  import { execa as execa4 } from "execa";
1637
1674
  var doctorCommand = new Command5("doctor").description("Diagnose NEXUS runtime environment").action(async () => {
1638
1675
  const platform = detectPlatform();
@@ -1662,11 +1699,11 @@ var doctorCommand = new Command5("doctor").description("Diagnose NEXUS runtime e
1662
1699
  } catch {
1663
1700
  }
1664
1701
  console.log(` ${check(isoTool)} ISO tool (mkisofs/genisoimage)`);
1665
- const keyExists = fs7.existsSync(path8.join(NEXUS_HOME2, "ssh", "id_nexus_vm"));
1702
+ const keyExists = fs8.existsSync(path9.join(NEXUS_HOME2, "ssh", "id_nexus_vm"));
1666
1703
  console.log(` ${check(keyExists)} SSH key`);
1667
1704
  const config = loadConfig();
1668
1705
  console.log(` ${check(!!config)} Configuration`);
1669
- const diskExists = fs7.existsSync(path8.join(NEXUS_HOME2, "vm", "images", "nexus-vm-disk.qcow2"));
1706
+ const diskExists = fs8.existsSync(path9.join(NEXUS_HOME2, "vm", "images", "nexus-vm-disk.qcow2"));
1670
1707
  console.log(` ${check(diskExists)} VM disk image`);
1671
1708
  if (config) {
1672
1709
  for (const [name, port] of [["SSH", config.sshPort], ["HTTP", config.httpPort], ["HTTPS", config.httpsPort]]) {
@@ -1687,7 +1724,7 @@ var doctorCommand = new Command5("doctor").description("Diagnose NEXUS runtime e
1687
1724
  }
1688
1725
  }
1689
1726
  }
1690
- const biosOk = fs7.existsSync(platform.biosPath);
1727
+ const biosOk = fs8.existsSync(platform.biosPath);
1691
1728
  console.log(` ${check(biosOk)} UEFI firmware ${biosOk ? "" : chalk8.dim(platform.biosPath)}`);
1692
1729
  console.log("");
1693
1730
  if (qemuOk && isoTool && biosOk) {
@@ -1738,18 +1775,18 @@ var logsCommand = new Command6("logs").description("View NEXUS server logs").opt
1738
1775
 
1739
1776
  // src/commands/update.ts
1740
1777
  import { Command as Command7 } from "commander";
1741
- import path9 from "path";
1742
- import fs8 from "fs";
1778
+ import path10 from "path";
1779
+ import fs9 from "fs";
1743
1780
  import { fileURLToPath as fileURLToPath2 } from "url";
1744
1781
  init_secrets();
1745
1782
  init_qemu();
1746
1783
  init_ssh();
1747
1784
  function getReleaseTarball2() {
1748
- const dir = path9.dirname(fileURLToPath2(import.meta.url));
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;
1785
+ const dir = path10.dirname(fileURLToPath2(import.meta.url));
1786
+ const tarballPath = path10.join(dir, "nexus-release.tar.gz");
1787
+ if (fs9.existsSync(tarballPath)) return tarballPath;
1788
+ const rootPath = path10.resolve(dir, "..", "dist", "nexus-release.tar.gz");
1789
+ if (fs9.existsSync(rootPath)) return rootPath;
1753
1790
  throw new Error("nexus-release.tar.gz not found. Reinstall buildwithnexus to get the latest release.");
1754
1791
  }
1755
1792
  var updateCommand = new Command7("update").description("Update NEXUS to the latest bundled release and restart").action(async () => {
@@ -1800,11 +1837,11 @@ var updateCommand = new Command7("update").description("Update NEXUS to the late
1800
1837
  // src/commands/destroy.ts
1801
1838
  import { Command as Command8 } from "commander";
1802
1839
  import chalk9 from "chalk";
1803
- import fs9 from "fs";
1840
+ import fs10 from "fs";
1804
1841
  import { input as input2 } from "@inquirer/prompts";
1805
1842
  init_secrets();
1806
1843
  init_qemu();
1807
- import path10 from "path";
1844
+ import path11 from "path";
1808
1845
  var destroyCommand = new Command8("destroy").description("Remove NEXUS VM and all data").option("--force", "Skip confirmation").action(async (opts) => {
1809
1846
  const config = loadConfig();
1810
1847
  if (!opts.force) {
@@ -1832,9 +1869,9 @@ var destroyCommand = new Command8("destroy").description("Remove NEXUS VM and al
1832
1869
  }
1833
1870
  stopVm();
1834
1871
  }
1835
- const sshConfigPath = path10.join(process.env.HOME || "~", ".ssh", "config");
1836
- if (fs9.existsSync(sshConfigPath)) {
1837
- const content = fs9.readFileSync(sshConfigPath, "utf-8");
1872
+ const sshConfigPath = path11.join(process.env.HOME || "~", ".ssh", "config");
1873
+ if (fs10.existsSync(sshConfigPath)) {
1874
+ const content = fs10.readFileSync(sshConfigPath, "utf-8");
1838
1875
  const lines = content.split("\n");
1839
1876
  const filtered = [];
1840
1877
  let skip = false;
@@ -1847,9 +1884,9 @@ var destroyCommand = new Command8("destroy").description("Remove NEXUS VM and al
1847
1884
  skip = false;
1848
1885
  filtered.push(line);
1849
1886
  }
1850
- fs9.writeFileSync(sshConfigPath, filtered.join("\n"));
1887
+ fs10.writeFileSync(sshConfigPath, filtered.join("\n"));
1851
1888
  }
1852
- fs9.rmSync(NEXUS_HOME2, { recursive: true, force: true });
1889
+ fs10.rmSync(NEXUS_HOME2, { recursive: true, force: true });
1853
1890
  succeed(spinner, "NEXUS runtime destroyed");
1854
1891
  log.dim("Run 'buildwithnexus init' to set up again");
1855
1892
  });
@@ -2262,10 +2299,10 @@ init_dlp();
2262
2299
  // src/ui/repl.ts
2263
2300
  init_secrets();
2264
2301
  import readline from "readline";
2265
- import fs10 from "fs";
2266
- import path11 from "path";
2302
+ import fs11 from "fs";
2303
+ import path12 from "path";
2267
2304
  import chalk13 from "chalk";
2268
- var HISTORY_FILE = path11.join(NEXUS_HOME2, "shell_history");
2305
+ var HISTORY_FILE = path12.join(NEXUS_HOME2, "shell_history");
2269
2306
  var MAX_HISTORY = 1e3;
2270
2307
  var Repl = class {
2271
2308
  rl = null;
@@ -2281,17 +2318,17 @@ var Repl = class {
2281
2318
  }
2282
2319
  loadHistory() {
2283
2320
  try {
2284
- if (fs10.existsSync(HISTORY_FILE)) {
2285
- this.history = fs10.readFileSync(HISTORY_FILE, "utf-8").split("\n").filter(Boolean).slice(-MAX_HISTORY);
2321
+ if (fs11.existsSync(HISTORY_FILE)) {
2322
+ this.history = fs11.readFileSync(HISTORY_FILE, "utf-8").split("\n").filter(Boolean).slice(-MAX_HISTORY);
2286
2323
  }
2287
2324
  } catch {
2288
2325
  }
2289
2326
  }
2290
2327
  saveHistory() {
2291
2328
  try {
2292
- const dir = path11.dirname(HISTORY_FILE);
2293
- fs10.mkdirSync(dir, { recursive: true });
2294
- fs10.writeFileSync(HISTORY_FILE, this.history.slice(-MAX_HISTORY).join("\n") + "\n", { mode: 384 });
2329
+ const dir = path12.dirname(HISTORY_FILE);
2330
+ fs11.mkdirSync(dir, { recursive: true });
2331
+ fs11.writeFileSync(HISTORY_FILE, this.history.slice(-MAX_HISTORY).join("\n") + "\n", { mode: 384 });
2295
2332
  } catch {
2296
2333
  }
2297
2334
  }
@@ -2906,7 +2943,7 @@ var shellCommand2 = new Command13("shell").description("Launch the interactive N
2906
2943
  });
2907
2944
 
2908
2945
  // src/cli.ts
2909
- var cli = new Command14().name("buildwithnexus").description("Auto-scaffold and launch a fully autonomous NEXUS runtime").version("0.5.1");
2946
+ var cli = new Command14().name("buildwithnexus").description("Auto-scaffold and launch a fully autonomous NEXUS runtime").version("0.5.4");
2910
2947
  cli.addCommand(initCommand);
2911
2948
  cli.addCommand(startCommand);
2912
2949
  cli.addCommand(stopCommand);
@@ -2935,18 +2972,18 @@ cli.action(async () => {
2935
2972
  });
2936
2973
 
2937
2974
  // src/core/update-notifier.ts
2938
- import fs11 from "fs";
2939
- import path12 from "path";
2940
- import os3 from "os";
2975
+ import fs12 from "fs";
2976
+ import path13 from "path";
2977
+ import os4 from "os";
2941
2978
  import https from "https";
2942
2979
  import chalk16 from "chalk";
2943
2980
  var PACKAGE_NAME = "buildwithnexus";
2944
2981
  var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
2945
- var STATE_DIR = path12.join(os3.homedir(), ".buildwithnexus");
2946
- var STATE_FILE = path12.join(STATE_DIR, ".update-check.json");
2982
+ var STATE_DIR = path13.join(os4.homedir(), ".buildwithnexus");
2983
+ var STATE_FILE = path13.join(STATE_DIR, ".update-check.json");
2947
2984
  function readState() {
2948
2985
  try {
2949
- const raw = fs11.readFileSync(STATE_FILE, "utf-8");
2986
+ const raw = fs12.readFileSync(STATE_FILE, "utf-8");
2950
2987
  return JSON.parse(raw);
2951
2988
  } catch {
2952
2989
  return { lastCheck: 0, latestVersion: null };
@@ -2954,8 +2991,8 @@ function readState() {
2954
2991
  }
2955
2992
  function writeState(state) {
2956
2993
  try {
2957
- fs11.mkdirSync(STATE_DIR, { recursive: true, mode: 448 });
2958
- fs11.writeFileSync(STATE_FILE, JSON.stringify(state), { mode: 384 });
2994
+ fs12.mkdirSync(STATE_DIR, { recursive: true, mode: 448 });
2995
+ fs12.writeFileSync(STATE_FILE, JSON.stringify(state), { mode: 384 });
2959
2996
  } catch {
2960
2997
  }
2961
2998
  }
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buildwithnexus",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
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"
22
+ "prepublishOnly": "npm run build && npm run bundle"
23
23
  },
24
24
  "engines": {
25
25
  "node": ">=18"