buildwithnexus 0.3.1 → 0.3.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 +549 -400
- package/dist/nexus-release.tar.gz +0 -0
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -363,7 +363,7 @@ __export(qemu_exports, {
|
|
|
363
363
|
import fs3 from "fs";
|
|
364
364
|
import net from "net";
|
|
365
365
|
import path3 from "path";
|
|
366
|
-
import { execa } from "execa";
|
|
366
|
+
import { execa, execaSync } from "execa";
|
|
367
367
|
import { select } from "@inquirer/prompts";
|
|
368
368
|
import chalk5 from "chalk";
|
|
369
369
|
async function isQemuInstalled(platform) {
|
|
@@ -378,10 +378,16 @@ async function installQemu(platform) {
|
|
|
378
378
|
if (platform.os === "mac") {
|
|
379
379
|
await execa("brew", ["install", "qemu", "cdrtools"], { stdio: "inherit", env: scrubEnv() });
|
|
380
380
|
} else if (platform.os === "linux") {
|
|
381
|
+
let hasApt = false;
|
|
381
382
|
try {
|
|
383
|
+
await execa("which", ["apt-get"], { env: scrubEnv() });
|
|
384
|
+
hasApt = true;
|
|
385
|
+
} catch {
|
|
386
|
+
}
|
|
387
|
+
if (hasApt) {
|
|
382
388
|
await execa("sudo", ["apt-get", "update"], { stdio: "inherit", env: scrubEnv() });
|
|
383
389
|
await execa("sudo", ["apt-get", "install", "-y", "qemu-system", "qemu-utils", "genisoimage"], { stdio: "inherit", env: scrubEnv() });
|
|
384
|
-
}
|
|
390
|
+
} else {
|
|
385
391
|
await execa("sudo", ["yum", "install", "-y", "qemu-system-arm", "qemu-system-x86", "qemu-img", "genisoimage"], { stdio: "inherit", env: scrubEnv() });
|
|
386
392
|
}
|
|
387
393
|
} else {
|
|
@@ -392,7 +398,7 @@ async function downloadImage(platform) {
|
|
|
392
398
|
const imagePath = path3.join(IMAGES_DIR, platform.ubuntuImage);
|
|
393
399
|
if (fs3.existsSync(imagePath)) return imagePath;
|
|
394
400
|
const url = `${UBUNTU_BASE_URL}/${platform.ubuntuImage}`;
|
|
395
|
-
await execa("curl", ["-L", "-o", imagePath, "--progress-bar", url], { stdio: "inherit", env: scrubEnv() });
|
|
401
|
+
await execa("curl", ["-L", "-C", "-", "-o", imagePath, "--progress-bar", url], { stdio: "inherit", env: scrubEnv() });
|
|
396
402
|
return imagePath;
|
|
397
403
|
}
|
|
398
404
|
async function createDisk(basePath, sizeGb) {
|
|
@@ -490,38 +496,36 @@ async function resolvePortConflicts(ports) {
|
|
|
490
496
|
return resolved;
|
|
491
497
|
}
|
|
492
498
|
async function launchVm(platform, diskPath, initIsoPath, ram, cpus, ports) {
|
|
493
|
-
const
|
|
499
|
+
const machineArgs = platform.os === "mac" ? ["-machine", "virt,gic-version=3"] : ["-machine", "pc"];
|
|
494
500
|
const biosArgs = fs3.existsSync(platform.biosPath) ? ["-bios", platform.biosPath] : [];
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
];
|
|
520
|
-
}
|
|
501
|
+
const buildArgs = (cpuArgs) => [
|
|
502
|
+
...machineArgs,
|
|
503
|
+
...cpuArgs,
|
|
504
|
+
"-m",
|
|
505
|
+
`${ram}G`,
|
|
506
|
+
"-smp",
|
|
507
|
+
`${cpus}`,
|
|
508
|
+
"-drive",
|
|
509
|
+
`file=${diskPath},if=virtio,cache=writethrough`,
|
|
510
|
+
"-drive",
|
|
511
|
+
`file=${initIsoPath},if=virtio,format=raw,cache=writethrough`,
|
|
512
|
+
"-display",
|
|
513
|
+
"none",
|
|
514
|
+
"-serial",
|
|
515
|
+
"none",
|
|
516
|
+
"-net",
|
|
517
|
+
"nic,model=virtio",
|
|
518
|
+
"-net",
|
|
519
|
+
`user,hostfwd=tcp::${ports.ssh}-:22,hostfwd=tcp::${ports.http}-:4200,hostfwd=tcp::${ports.https}-:443`,
|
|
520
|
+
...biosArgs,
|
|
521
|
+
"-pidfile",
|
|
522
|
+
PID_FILE,
|
|
523
|
+
"-daemonize"
|
|
524
|
+
];
|
|
521
525
|
try {
|
|
522
|
-
await execa(platform.qemuBinary, buildArgs(platform.qemuCpuFlag), { env: scrubEnv() });
|
|
526
|
+
await execa(platform.qemuBinary, buildArgs(platform.qemuCpuFlag.split(" ")), { env: scrubEnv() });
|
|
523
527
|
} catch {
|
|
524
|
-
const fallbackCpu = platform.os === "mac" ? "-cpu max" : "-cpu qemu64";
|
|
528
|
+
const fallbackCpu = platform.os === "mac" ? ["-cpu", "max"] : ["-cpu", "qemu64"];
|
|
525
529
|
await execa(platform.qemuBinary, buildArgs(fallbackCpu), { env: scrubEnv() });
|
|
526
530
|
}
|
|
527
531
|
return ports;
|
|
@@ -533,6 +537,14 @@ function readValidPid() {
|
|
|
533
537
|
if (!Number.isInteger(pid) || pid <= 1 || pid > 4194304) return null;
|
|
534
538
|
return pid;
|
|
535
539
|
}
|
|
540
|
+
function isQemuPid(pid) {
|
|
541
|
+
try {
|
|
542
|
+
const { stdout } = execaSync("ps", ["-p", String(pid), "-o", "comm="], { env: scrubEnv() });
|
|
543
|
+
return stdout.trim().toLowerCase().includes("qemu");
|
|
544
|
+
} catch {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
536
548
|
function isVmRunning() {
|
|
537
549
|
const pid = readValidPid();
|
|
538
550
|
if (!pid) return false;
|
|
@@ -546,6 +558,13 @@ function isVmRunning() {
|
|
|
546
558
|
function stopVm() {
|
|
547
559
|
const pid = readValidPid();
|
|
548
560
|
if (!pid) return;
|
|
561
|
+
if (!isQemuPid(pid)) {
|
|
562
|
+
try {
|
|
563
|
+
fs3.unlinkSync(PID_FILE);
|
|
564
|
+
} catch {
|
|
565
|
+
}
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
549
568
|
try {
|
|
550
569
|
process.kill(pid, "SIGTERM");
|
|
551
570
|
} catch {
|
|
@@ -722,6 +741,7 @@ import { Command as Command14 } from "commander";
|
|
|
722
741
|
// src/commands/init.ts
|
|
723
742
|
init_banner();
|
|
724
743
|
import { Command } from "commander";
|
|
744
|
+
import chalk6 from "chalk";
|
|
725
745
|
|
|
726
746
|
// src/ui/spinner.ts
|
|
727
747
|
import ora from "ora";
|
|
@@ -928,69 +948,65 @@ async function createCloudInitIso(userDataPath) {
|
|
|
928
948
|
fs5.writeFileSync(metaDataPath, "instance-id: nexus-vm-1\nlocal-hostname: nexus-vm\n", { mode: 384 });
|
|
929
949
|
const isoPath = path5.join(IMAGES_DIR2, "init.iso");
|
|
930
950
|
const env = scrubEnv();
|
|
931
|
-
let created = false;
|
|
932
951
|
try {
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
952
|
+
let created = false;
|
|
953
|
+
for (const tool of ["mkisofs", "genisoimage"]) {
|
|
954
|
+
if (created) break;
|
|
955
|
+
try {
|
|
956
|
+
await execa3(tool, [
|
|
957
|
+
"-output",
|
|
958
|
+
isoPath,
|
|
959
|
+
"-volid",
|
|
960
|
+
"cidata",
|
|
961
|
+
"-joliet",
|
|
962
|
+
"-rock",
|
|
963
|
+
userDataPath,
|
|
964
|
+
metaDataPath
|
|
965
|
+
], { env });
|
|
966
|
+
created = true;
|
|
967
|
+
} catch {
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
if (!created) {
|
|
971
|
+
try {
|
|
972
|
+
const stagingDir = path5.join(CONFIGS_DIR, "cidata-staging");
|
|
973
|
+
fs5.mkdirSync(stagingDir, { recursive: true, mode: 448 });
|
|
974
|
+
fs5.copyFileSync(userDataPath, path5.join(stagingDir, "user-data"));
|
|
975
|
+
fs5.copyFileSync(metaDataPath, path5.join(stagingDir, "meta-data"));
|
|
976
|
+
await execa3("hdiutil", [
|
|
977
|
+
"makehybrid",
|
|
978
|
+
"-o",
|
|
979
|
+
isoPath,
|
|
980
|
+
"-joliet",
|
|
981
|
+
"-iso",
|
|
982
|
+
"-default-volume-name",
|
|
983
|
+
"cidata",
|
|
984
|
+
stagingDir
|
|
985
|
+
], { env });
|
|
986
|
+
fs5.rmSync(stagingDir, { recursive: true, force: true });
|
|
987
|
+
created = true;
|
|
988
|
+
} catch {
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
if (!created) {
|
|
992
|
+
throw new Error(
|
|
993
|
+
"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"
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
fs5.chmodSync(isoPath, 384);
|
|
997
|
+
audit("cloudinit_iso_created", "init.iso created");
|
|
998
|
+
return isoPath;
|
|
999
|
+
} finally {
|
|
947
1000
|
try {
|
|
948
|
-
|
|
949
|
-
"-output",
|
|
950
|
-
isoPath,
|
|
951
|
-
"-volid",
|
|
952
|
-
"cidata",
|
|
953
|
-
"-joliet",
|
|
954
|
-
"-rock",
|
|
955
|
-
userDataPath,
|
|
956
|
-
metaDataPath
|
|
957
|
-
], { env });
|
|
958
|
-
created = true;
|
|
1001
|
+
fs5.unlinkSync(userDataPath);
|
|
959
1002
|
} catch {
|
|
960
1003
|
}
|
|
961
|
-
}
|
|
962
|
-
if (!created) {
|
|
963
1004
|
try {
|
|
964
|
-
|
|
965
|
-
fs5.mkdirSync(stagingDir, { recursive: true, mode: 448 });
|
|
966
|
-
fs5.copyFileSync(userDataPath, path5.join(stagingDir, "user-data"));
|
|
967
|
-
fs5.copyFileSync(metaDataPath, path5.join(stagingDir, "meta-data"));
|
|
968
|
-
await execa3("hdiutil", [
|
|
969
|
-
"makehybrid",
|
|
970
|
-
"-o",
|
|
971
|
-
isoPath,
|
|
972
|
-
"-joliet",
|
|
973
|
-
"-iso",
|
|
974
|
-
"-default-volume-name",
|
|
975
|
-
"cidata",
|
|
976
|
-
stagingDir
|
|
977
|
-
], { env });
|
|
978
|
-
fs5.rmSync(stagingDir, { recursive: true, force: true });
|
|
979
|
-
created = true;
|
|
1005
|
+
fs5.unlinkSync(metaDataPath);
|
|
980
1006
|
} catch {
|
|
981
1007
|
}
|
|
1008
|
+
audit("cloudinit_plaintext_deleted", "user-data.yaml and meta-data.yaml removed");
|
|
982
1009
|
}
|
|
983
|
-
if (!created) {
|
|
984
|
-
throw new Error(
|
|
985
|
-
"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"
|
|
986
|
-
);
|
|
987
|
-
}
|
|
988
|
-
fs5.chmodSync(isoPath, 384);
|
|
989
|
-
fs5.unlinkSync(userDataPath);
|
|
990
|
-
fs5.unlinkSync(metaDataPath);
|
|
991
|
-
audit("cloudinit_plaintext_deleted", "user-data.yaml and meta-data.yaml removed");
|
|
992
|
-
audit("cloudinit_iso_created", "init.iso created");
|
|
993
|
-
return isoPath;
|
|
994
1010
|
}
|
|
995
1011
|
|
|
996
1012
|
// src/core/health.ts
|
|
@@ -1029,27 +1045,51 @@ async function checkHealth(port, vmRunning) {
|
|
|
1029
1045
|
}
|
|
1030
1046
|
return status;
|
|
1031
1047
|
}
|
|
1032
|
-
async function waitForServer(port, timeoutMs =
|
|
1048
|
+
async function waitForServer(port, timeoutMs = 9e5) {
|
|
1033
1049
|
const start = Date.now();
|
|
1050
|
+
let lastLog = 0;
|
|
1034
1051
|
while (Date.now() - start < timeoutMs) {
|
|
1035
1052
|
try {
|
|
1036
1053
|
const { stdout, code } = await sshExec(port, "curl -sf http://localhost:4200/health");
|
|
1037
1054
|
if (code === 0 && stdout.includes("ok")) return true;
|
|
1038
1055
|
} catch {
|
|
1039
1056
|
}
|
|
1040
|
-
|
|
1057
|
+
const elapsed = Date.now() - start;
|
|
1058
|
+
if (elapsed - lastLog >= 3e4) {
|
|
1059
|
+
lastLog = elapsed;
|
|
1060
|
+
try {
|
|
1061
|
+
const { stdout } = await sshExec(port, "systemctl is-active nexus 2>/dev/null || echo 'starting...'");
|
|
1062
|
+
process.stderr.write(`
|
|
1063
|
+
[server ${Math.round(elapsed / 1e3)}s] ${stdout.trim().slice(0, 120)}
|
|
1064
|
+
`);
|
|
1065
|
+
} catch {
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
await new Promise((r) => setTimeout(r, 5e3));
|
|
1041
1069
|
}
|
|
1042
1070
|
return false;
|
|
1043
1071
|
}
|
|
1044
|
-
async function waitForCloudInit(port, timeoutMs =
|
|
1072
|
+
async function waitForCloudInit(port, timeoutMs = 18e5) {
|
|
1045
1073
|
const start = Date.now();
|
|
1074
|
+
let lastLog = 0;
|
|
1046
1075
|
while (Date.now() - start < timeoutMs) {
|
|
1047
1076
|
try {
|
|
1048
1077
|
const { code } = await sshExec(port, "test -f /var/lib/cloud/instance/boot-finished");
|
|
1049
1078
|
if (code === 0) return true;
|
|
1050
1079
|
} catch {
|
|
1051
1080
|
}
|
|
1052
|
-
|
|
1081
|
+
const elapsed = Date.now() - start;
|
|
1082
|
+
if (elapsed - lastLog >= 6e4) {
|
|
1083
|
+
lastLog = elapsed;
|
|
1084
|
+
try {
|
|
1085
|
+
const { stdout } = await sshExec(port, "tail -1 /var/log/cloud-init-output.log 2>/dev/null || echo 'waiting...'");
|
|
1086
|
+
process.stderr.write(`
|
|
1087
|
+
[cloud-init ${Math.round(elapsed / 1e3)}s] ${stdout.trim().slice(0, 120)}
|
|
1088
|
+
`);
|
|
1089
|
+
} catch {
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
await new Promise((r) => setTimeout(r, 2e4));
|
|
1053
1093
|
}
|
|
1054
1094
|
return false;
|
|
1055
1095
|
}
|
|
@@ -1076,17 +1116,19 @@ async function installCloudflared(sshPort, arch) {
|
|
|
1076
1116
|
}
|
|
1077
1117
|
async function startTunnel(sshPort) {
|
|
1078
1118
|
await sshExec(sshPort, [
|
|
1079
|
-
"
|
|
1080
|
-
"
|
|
1119
|
+
"install -m 600 /dev/null /home/nexus/.nexus/tunnel.log",
|
|
1120
|
+
"&& nohup cloudflared tunnel --no-autoupdate --url http://localhost:4200",
|
|
1121
|
+
"> /home/nexus/.nexus/tunnel.log 2>&1 &",
|
|
1081
1122
|
"disown"
|
|
1082
1123
|
].join(" "));
|
|
1083
1124
|
const start = Date.now();
|
|
1084
|
-
while (Date.now() - start <
|
|
1125
|
+
while (Date.now() - start < 6e4) {
|
|
1085
1126
|
try {
|
|
1086
|
-
const { stdout } = await sshExec(sshPort, "grep -
|
|
1127
|
+
const { stdout } = await sshExec(sshPort, "grep -oE 'https://[a-z0-9-]+\\.trycloudflare\\.com' /home/nexus/.nexus/tunnel.log 2>/dev/null | head -1");
|
|
1087
1128
|
if (stdout.includes("https://")) {
|
|
1088
1129
|
const url = stdout.trim();
|
|
1089
1130
|
if (!/^https:\/\/[a-z0-9-]+\.trycloudflare\.com$/.test(url)) {
|
|
1131
|
+
audit("tunnel_url_rejected", `Invalid URL format: ${url.slice(0, 80)}`);
|
|
1090
1132
|
return null;
|
|
1091
1133
|
}
|
|
1092
1134
|
await sshExec(sshPort, shellCommand`printf '%s\n' ${url} > /home/nexus/.nexus/tunnel-url.txt && chmod 600 /home/nexus/.nexus/tunnel-url.txt`);
|
|
@@ -1119,176 +1161,283 @@ function getReleaseTarball() {
|
|
|
1119
1161
|
if (fs6.existsSync(rootPath)) return rootPath;
|
|
1120
1162
|
throw new Error("nexus-release.tar.gz not found. Run: npm run bundle");
|
|
1121
1163
|
}
|
|
1122
|
-
var TOTAL_PHASES = 10;
|
|
1123
1164
|
function getCloudInitTemplate() {
|
|
1124
1165
|
const dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
1125
1166
|
const templatePath = path6.join(dir, "templates", "cloud-init.yaml.ejs");
|
|
1126
|
-
if (fs6.existsSync(templatePath))
|
|
1127
|
-
return fs6.readFileSync(templatePath, "utf-8");
|
|
1128
|
-
}
|
|
1167
|
+
if (fs6.existsSync(templatePath)) return fs6.readFileSync(templatePath, "utf-8");
|
|
1129
1168
|
const srcPath = path6.resolve(dir, "..", "src", "templates", "cloud-init.yaml.ejs");
|
|
1130
1169
|
return fs6.readFileSync(srcPath, "utf-8");
|
|
1131
1170
|
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
masterSecret
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1171
|
+
async function withSpinner(spinner, label, fn) {
|
|
1172
|
+
spinner.text = label;
|
|
1173
|
+
spinner.start();
|
|
1174
|
+
const result = await fn();
|
|
1175
|
+
succeed(spinner, label);
|
|
1176
|
+
return result;
|
|
1177
|
+
}
|
|
1178
|
+
var phases = [
|
|
1179
|
+
// Phase 1 — Configuration
|
|
1180
|
+
{
|
|
1181
|
+
name: "Configuration",
|
|
1182
|
+
run: async (ctx) => {
|
|
1183
|
+
showBanner();
|
|
1184
|
+
const platform = detectPlatform();
|
|
1185
|
+
log.detail("Platform", `${platform.os} ${platform.arch}`);
|
|
1186
|
+
log.detail("QEMU", platform.qemuBinary);
|
|
1187
|
+
const userConfig = await promptInitConfig();
|
|
1188
|
+
ensureHome();
|
|
1189
|
+
const masterSecret = generateMasterSecret();
|
|
1190
|
+
const config = {
|
|
1191
|
+
vmRam: userConfig.vmRam,
|
|
1192
|
+
vmCpus: userConfig.vmCpus,
|
|
1193
|
+
vmDisk: userConfig.vmDisk,
|
|
1194
|
+
enableTunnel: userConfig.enableTunnel,
|
|
1195
|
+
sshPort: 2222,
|
|
1196
|
+
httpPort: 4200,
|
|
1197
|
+
httpsPort: 8443,
|
|
1198
|
+
masterSecret
|
|
1199
|
+
};
|
|
1200
|
+
const keys = {
|
|
1201
|
+
ANTHROPIC_API_KEY: userConfig.anthropicKey,
|
|
1202
|
+
OPENAI_API_KEY: userConfig.openaiKey || void 0,
|
|
1203
|
+
GOOGLE_API_KEY: userConfig.googleKey || void 0,
|
|
1204
|
+
NEXUS_MASTER_SECRET: masterSecret
|
|
1205
|
+
};
|
|
1206
|
+
const violations = validateAllKeys(keys);
|
|
1207
|
+
if (violations.length > 0) {
|
|
1208
|
+
for (const v of violations) log.error(v);
|
|
1209
|
+
process.exit(1);
|
|
1210
|
+
}
|
|
1211
|
+
saveConfig(config);
|
|
1212
|
+
saveKeys(keys);
|
|
1213
|
+
log.success("Configuration saved");
|
|
1214
|
+
ctx.platform = platform;
|
|
1215
|
+
ctx.config = config;
|
|
1216
|
+
ctx.keys = keys;
|
|
1162
1217
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1218
|
+
},
|
|
1219
|
+
// Phase 2 — QEMU
|
|
1220
|
+
{
|
|
1221
|
+
name: "QEMU Installation",
|
|
1222
|
+
run: async (ctx, spinner) => {
|
|
1223
|
+
const { platform } = ctx;
|
|
1224
|
+
await withSpinner(spinner, "Checking QEMU...", async () => {
|
|
1225
|
+
const hasQemu = await isQemuInstalled(platform);
|
|
1226
|
+
if (!hasQemu) {
|
|
1227
|
+
spinner.text = "Installing QEMU...";
|
|
1228
|
+
await installQemu(platform);
|
|
1229
|
+
}
|
|
1230
|
+
});
|
|
1231
|
+
succeed(spinner, hasQemuLabel(await isQemuInstalled(ctx.platform)));
|
|
1176
1232
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
succeed(spinner, "Ubuntu image ready");
|
|
1188
|
-
showPhase(5, TOTAL_PHASES, "Cloud-Init Generation");
|
|
1189
|
-
spinner = createSpinner("Locating release tarball...");
|
|
1190
|
-
spinner.start();
|
|
1191
|
-
const tarballPath = getReleaseTarball();
|
|
1192
|
-
succeed(spinner, `Release tarball found (${path6.basename(tarballPath)})`);
|
|
1193
|
-
spinner = createSpinner("Rendering cloud-init...");
|
|
1194
|
-
spinner.start();
|
|
1195
|
-
const pubKey = getPubKey();
|
|
1196
|
-
const template = getCloudInitTemplate();
|
|
1197
|
-
const userDataPath = await renderCloudInit({ sshPubKey: pubKey, keys, config }, template);
|
|
1198
|
-
const isoPath = await createCloudInitIso(userDataPath);
|
|
1199
|
-
succeed(spinner, "Cloud-init ISO created");
|
|
1200
|
-
showPhase(6, TOTAL_PHASES, "VM Launch");
|
|
1201
|
-
spinner = createSpinner("Checking port availability...");
|
|
1202
|
-
spinner.start();
|
|
1203
|
-
const requestedPorts = { ssh: config.sshPort, http: config.httpPort, https: config.httpsPort };
|
|
1204
|
-
spinner.stop();
|
|
1205
|
-
spinner.clear();
|
|
1206
|
-
const resolvedPorts = await resolvePortConflicts(requestedPorts);
|
|
1207
|
-
spinner = createSpinner("Creating disk and launching VM...");
|
|
1208
|
-
spinner.start();
|
|
1209
|
-
const diskPath = await createDisk(imagePath, config.vmDisk);
|
|
1210
|
-
await launchVm(platform, diskPath, isoPath, config.vmRam, config.vmCpus, resolvedPorts);
|
|
1211
|
-
config.sshPort = resolvedPorts.ssh;
|
|
1212
|
-
config.httpPort = resolvedPorts.http;
|
|
1213
|
-
config.httpsPort = resolvedPorts.https;
|
|
1214
|
-
saveConfig(config);
|
|
1215
|
-
const portNote = resolvedPorts.ssh !== 2222 || resolvedPorts.http !== 4200 || resolvedPorts.https !== 8443 ? ` (ports: SSH=${resolvedPorts.ssh}, HTTP=${resolvedPorts.http}, HTTPS=${resolvedPorts.https})` : "";
|
|
1216
|
-
succeed(spinner, `VM launched (daemonized)${portNote}`);
|
|
1217
|
-
showPhase(7, TOTAL_PHASES, "VM Provisioning");
|
|
1218
|
-
spinner = createSpinner("Waiting for SSH...");
|
|
1219
|
-
spinner.start();
|
|
1220
|
-
const sshReady = await waitForSsh(config.sshPort);
|
|
1221
|
-
if (!sshReady) {
|
|
1222
|
-
fail(spinner, "SSH connection timed out");
|
|
1223
|
-
process.exit(1);
|
|
1233
|
+
},
|
|
1234
|
+
// Phase 3 — SSH Keys
|
|
1235
|
+
{
|
|
1236
|
+
name: "SSH Key Setup",
|
|
1237
|
+
run: async (ctx, spinner) => {
|
|
1238
|
+
const { config } = ctx;
|
|
1239
|
+
await withSpinner(spinner, "Generating SSH key...", async () => {
|
|
1240
|
+
await generateSshKey();
|
|
1241
|
+
addSshConfig(config.sshPort);
|
|
1242
|
+
});
|
|
1224
1243
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
await sshExec(config.sshPort, "chmod 600 /tmp/.nexus-env-keys");
|
|
1238
|
-
} finally {
|
|
1239
|
-
try {
|
|
1240
|
-
fs6.writeFileSync(tmpKeysPath, "0".repeat(keysContent.length));
|
|
1241
|
-
fs6.unlinkSync(tmpKeysPath);
|
|
1242
|
-
} catch {
|
|
1243
|
-
}
|
|
1244
|
+
},
|
|
1245
|
+
// Phase 4 — Ubuntu Image
|
|
1246
|
+
{
|
|
1247
|
+
name: "VM Image Download",
|
|
1248
|
+
run: async (ctx, spinner) => {
|
|
1249
|
+
const { platform } = ctx;
|
|
1250
|
+
const imagePath = await withSpinner(
|
|
1251
|
+
spinner,
|
|
1252
|
+
`Downloading Ubuntu 24.04 (${platform.ubuntuImage})...`,
|
|
1253
|
+
() => downloadImage(platform)
|
|
1254
|
+
);
|
|
1255
|
+
ctx.imagePath = imagePath;
|
|
1244
1256
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1257
|
+
},
|
|
1258
|
+
// Phase 5 — Cloud-Init
|
|
1259
|
+
{
|
|
1260
|
+
name: "Cloud-Init Generation",
|
|
1261
|
+
run: async (ctx, spinner) => {
|
|
1262
|
+
const { keys, config } = ctx;
|
|
1263
|
+
const tarballPath = await withSpinner(spinner, "Locating release tarball...", async () => {
|
|
1264
|
+
return getReleaseTarball();
|
|
1265
|
+
});
|
|
1266
|
+
ctx.tarballPath = tarballPath;
|
|
1267
|
+
const isoPath = await withSpinner(spinner, "Rendering cloud-init...", async () => {
|
|
1268
|
+
const pubKey = getPubKey();
|
|
1269
|
+
const template = getCloudInitTemplate();
|
|
1270
|
+
const userDataPath = await renderCloudInit({ sshPubKey: pubKey, keys, config }, template);
|
|
1271
|
+
return createCloudInitIso(userDataPath);
|
|
1272
|
+
});
|
|
1273
|
+
ctx.isoPath = isoPath;
|
|
1253
1274
|
}
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1275
|
+
},
|
|
1276
|
+
// Phase 6 — VM Boot
|
|
1277
|
+
{
|
|
1278
|
+
name: "VM Launch",
|
|
1279
|
+
run: async (ctx, spinner) => {
|
|
1280
|
+
const { platform, imagePath, isoPath, config } = ctx;
|
|
1281
|
+
spinner.text = "Checking port availability...";
|
|
1282
|
+
spinner.start();
|
|
1283
|
+
spinner.stop();
|
|
1284
|
+
spinner.clear();
|
|
1285
|
+
const requestedPorts = { ssh: config.sshPort, http: config.httpPort, https: config.httpsPort };
|
|
1286
|
+
const resolvedPorts = await resolvePortConflicts(requestedPorts);
|
|
1287
|
+
const diskPath = await withSpinner(spinner, "Creating disk and launching VM...", async () => {
|
|
1288
|
+
const disk = await createDisk(imagePath, config.vmDisk);
|
|
1289
|
+
await launchVm(platform, disk, isoPath, config.vmRam, config.vmCpus, resolvedPorts);
|
|
1290
|
+
return disk;
|
|
1291
|
+
});
|
|
1292
|
+
config.sshPort = resolvedPorts.ssh;
|
|
1293
|
+
config.httpPort = resolvedPorts.http;
|
|
1294
|
+
config.httpsPort = resolvedPorts.https;
|
|
1295
|
+
saveConfig(config);
|
|
1296
|
+
const portNote = resolvedPorts.ssh !== 2222 || resolvedPorts.http !== 4200 || resolvedPorts.https !== 8443 ? ` (ports: SSH=${resolvedPorts.ssh}, HTTP=${resolvedPorts.http}, HTTPS=${resolvedPorts.https})` : "";
|
|
1297
|
+
succeed(spinner, `VM launched (daemonized)${portNote}`);
|
|
1298
|
+
ctx.diskPath = diskPath;
|
|
1299
|
+
ctx.resolvedPorts = resolvedPorts;
|
|
1300
|
+
ctx.vmLaunched = true;
|
|
1267
1301
|
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1302
|
+
},
|
|
1303
|
+
// Phase 7 — VM Provisioning
|
|
1304
|
+
{
|
|
1305
|
+
name: "VM Provisioning",
|
|
1306
|
+
run: async (ctx, spinner) => {
|
|
1307
|
+
const { config, keys, tarballPath } = ctx;
|
|
1308
|
+
const pinPath = path6.join(process.env.HOME || "~", ".buildwithnexus", "ssh", "vm_host_key.pin");
|
|
1309
|
+
if (fs6.existsSync(pinPath)) fs6.unlinkSync(pinPath);
|
|
1310
|
+
spinner.text = "Waiting for SSH...";
|
|
1311
|
+
spinner.start();
|
|
1312
|
+
const sshReady = await waitForSsh(config.sshPort);
|
|
1313
|
+
if (!sshReady) {
|
|
1314
|
+
fail(spinner, "SSH connection timed out");
|
|
1315
|
+
throw new Error("SSH connection timed out");
|
|
1316
|
+
}
|
|
1317
|
+
succeed(spinner, "SSH connected");
|
|
1318
|
+
await withSpinner(
|
|
1319
|
+
spinner,
|
|
1320
|
+
"Uploading NEXUS release tarball...",
|
|
1321
|
+
() => sshUploadFile(config.sshPort, tarballPath, "/tmp/nexus-release.tar.gz")
|
|
1322
|
+
);
|
|
1323
|
+
await withSpinner(spinner, "Staging API keys...", async () => {
|
|
1324
|
+
const keysContent = Object.entries(keys).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
|
|
1325
|
+
const tmpKeysPath = path6.join(os2.tmpdir(), `.nexus-keys-${crypto4.randomBytes(8).toString("hex")}`);
|
|
1326
|
+
fs6.writeFileSync(tmpKeysPath, keysContent, { mode: 384 });
|
|
1327
|
+
try {
|
|
1328
|
+
await sshUploadFile(config.sshPort, tmpKeysPath, "/tmp/.nexus-env-keys");
|
|
1329
|
+
await sshExec(config.sshPort, "chmod 600 /tmp/.nexus-env-keys");
|
|
1330
|
+
} finally {
|
|
1331
|
+
try {
|
|
1332
|
+
fs6.writeFileSync(tmpKeysPath, "0".repeat(keysContent.length));
|
|
1333
|
+
fs6.unlinkSync(tmpKeysPath);
|
|
1334
|
+
} catch {
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1338
|
+
spinner.text = "Cloud-init provisioning \u2014 this takes 10-20 min (extracting NEXUS, building Docker, installing deps)...";
|
|
1339
|
+
spinner.start();
|
|
1340
|
+
const cloudInitDone = await waitForCloudInit(config.sshPort);
|
|
1341
|
+
if (!cloudInitDone) {
|
|
1342
|
+
fail(spinner, "Cloud-init timed out after 30 minutes");
|
|
1343
|
+
log.warn("Check progress: buildwithnexus ssh \u2192 tail -f /var/log/cloud-init-output.log");
|
|
1344
|
+
throw new Error("Cloud-init timed out");
|
|
1345
|
+
}
|
|
1346
|
+
succeed(spinner, "VM fully provisioned");
|
|
1347
|
+
await withSpinner(
|
|
1348
|
+
spinner,
|
|
1349
|
+
"Delivering API keys...",
|
|
1350
|
+
() => sshExec(
|
|
1351
|
+
config.sshPort,
|
|
1352
|
+
"mkdir -p /home/nexus/.nexus && mv /tmp/.nexus-env-keys /home/nexus/.nexus/.env.keys && chown -R nexus:nexus /home/nexus/.nexus && chmod 600 /home/nexus/.nexus/.env.keys"
|
|
1353
|
+
)
|
|
1354
|
+
);
|
|
1355
|
+
}
|
|
1356
|
+
},
|
|
1357
|
+
// Phase 8 — Server Health
|
|
1358
|
+
{
|
|
1359
|
+
name: "NEXUS Server Startup",
|
|
1360
|
+
run: async (ctx, spinner) => {
|
|
1361
|
+
const { config } = ctx;
|
|
1362
|
+
spinner.text = "Waiting for NEXUS server...";
|
|
1273
1363
|
spinner.start();
|
|
1274
|
-
await
|
|
1364
|
+
const serverReady = await waitForServer(config.sshPort);
|
|
1365
|
+
if (!serverReady) {
|
|
1366
|
+
fail(spinner, "Server failed to start");
|
|
1367
|
+
log.warn("Check logs: buildwithnexus logs");
|
|
1368
|
+
throw new Error("NEXUS server failed to start");
|
|
1369
|
+
}
|
|
1370
|
+
succeed(spinner, "NEXUS server healthy on port 4200");
|
|
1371
|
+
}
|
|
1372
|
+
},
|
|
1373
|
+
// Phase 9 — Cloudflare Tunnel
|
|
1374
|
+
{
|
|
1375
|
+
name: "Cloudflare Tunnel",
|
|
1376
|
+
run: async (ctx, spinner) => {
|
|
1377
|
+
const { config, platform } = ctx;
|
|
1378
|
+
if (!config.enableTunnel) {
|
|
1379
|
+
log.dim("Skipped (not enabled)");
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
await withSpinner(
|
|
1383
|
+
spinner,
|
|
1384
|
+
"Installing cloudflared...",
|
|
1385
|
+
() => installCloudflared(config.sshPort, platform.arch)
|
|
1386
|
+
);
|
|
1275
1387
|
spinner.text = "Starting tunnel...";
|
|
1388
|
+
spinner.start();
|
|
1276
1389
|
const url = await startTunnel(config.sshPort);
|
|
1277
1390
|
if (url) {
|
|
1278
|
-
tunnelUrl = url;
|
|
1391
|
+
ctx.tunnelUrl = url;
|
|
1279
1392
|
succeed(spinner, `Tunnel active: ${url}`);
|
|
1280
1393
|
} else {
|
|
1281
1394
|
fail(spinner, "Tunnel failed to start (server still accessible locally)");
|
|
1282
1395
|
}
|
|
1283
|
-
} else {
|
|
1284
|
-
showPhase(9, TOTAL_PHASES, "Cloudflare Tunnel");
|
|
1285
|
-
log.dim("Skipped (not enabled)");
|
|
1286
1396
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1397
|
+
},
|
|
1398
|
+
// Phase 10 — Complete
|
|
1399
|
+
{
|
|
1400
|
+
name: "Complete",
|
|
1401
|
+
run: async (ctx) => {
|
|
1402
|
+
showCompletion({ remote: ctx.tunnelUrl, ssh: "buildwithnexus ssh" });
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
];
|
|
1406
|
+
function hasQemuLabel(installed) {
|
|
1407
|
+
return installed ? "QEMU ready" : "QEMU installed";
|
|
1408
|
+
}
|
|
1409
|
+
var TOTAL_PHASES = phases.length;
|
|
1410
|
+
async function runInit() {
|
|
1411
|
+
const ctx = { vmLaunched: false, tunnelUrl: void 0 };
|
|
1412
|
+
const spinner = createSpinner("");
|
|
1413
|
+
for (let i = 0; i < phases.length; i++) {
|
|
1414
|
+
const phase = phases[i];
|
|
1415
|
+
showPhase(i + 1, TOTAL_PHASES, phase.name);
|
|
1416
|
+
if (phase.skip?.(ctx)) {
|
|
1417
|
+
log.dim("Skipped");
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
try {
|
|
1421
|
+
await phase.run(ctx, spinner);
|
|
1422
|
+
} catch (err) {
|
|
1423
|
+
try {
|
|
1424
|
+
spinner.stop();
|
|
1425
|
+
} catch {
|
|
1426
|
+
}
|
|
1427
|
+
if (ctx.vmLaunched) {
|
|
1428
|
+
process.stderr.write(chalk6.dim("\n Stopping VM due to init failure...\n"));
|
|
1429
|
+
try {
|
|
1430
|
+
stopVm();
|
|
1431
|
+
} catch {
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
throw err;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
var initCommand = new Command("init").description("Scaffold and launch a new NEXUS runtime").action(async () => {
|
|
1439
|
+
try {
|
|
1440
|
+
await runInit();
|
|
1292
1441
|
} catch (err) {
|
|
1293
1442
|
const safeErr = redactError(err);
|
|
1294
1443
|
log.error(`Init failed: ${safeErr.message}`);
|
|
@@ -1394,7 +1543,7 @@ var stopCommand = new Command3("stop").description("Gracefully shut down the NEX
|
|
|
1394
1543
|
|
|
1395
1544
|
// src/commands/status.ts
|
|
1396
1545
|
import { Command as Command4 } from "commander";
|
|
1397
|
-
import
|
|
1546
|
+
import chalk7 from "chalk";
|
|
1398
1547
|
init_secrets();
|
|
1399
1548
|
init_qemu();
|
|
1400
1549
|
var statusCommand = new Command4("status").description("Check NEXUS runtime health").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -1409,15 +1558,15 @@ var statusCommand = new Command4("status").description("Check NEXUS runtime heal
|
|
|
1409
1558
|
console.log(JSON.stringify({ ...health, pid: getVmPid(), ports: { ssh: config.sshPort, http: config.httpPort, https: config.httpsPort } }, null, 2));
|
|
1410
1559
|
return;
|
|
1411
1560
|
}
|
|
1412
|
-
const check = (ok) => ok ?
|
|
1561
|
+
const check = (ok) => ok ? chalk7.green("\u25CF") : chalk7.red("\u25CB");
|
|
1413
1562
|
console.log("");
|
|
1414
|
-
console.log(
|
|
1563
|
+
console.log(chalk7.bold(" NEXUS Runtime Status"));
|
|
1415
1564
|
console.log("");
|
|
1416
|
-
console.log(` ${check(health.vmRunning)} VM ${health.vmRunning ?
|
|
1417
|
-
console.log(` ${check(health.sshReady)} SSH ${health.sshReady ?
|
|
1418
|
-
console.log(` ${check(health.dockerReady)} Docker ${health.dockerReady ?
|
|
1419
|
-
console.log(` ${check(health.serverHealthy)} Server ${health.serverHealthy ?
|
|
1420
|
-
console.log(` ${check(!!health.tunnelUrl)} Tunnel ${health.tunnelUrl ?
|
|
1565
|
+
console.log(` ${check(health.vmRunning)} VM ${health.vmRunning ? chalk7.green("running") + chalk7.dim(` (PID ${getVmPid()})`) : chalk7.red("stopped")}`);
|
|
1566
|
+
console.log(` ${check(health.sshReady)} SSH ${health.sshReady ? chalk7.green("connected") + chalk7.dim(` (port ${config.sshPort})`) : chalk7.red("unreachable")}`);
|
|
1567
|
+
console.log(` ${check(health.dockerReady)} Docker ${health.dockerReady ? chalk7.green("ready") : chalk7.red("not ready")}`);
|
|
1568
|
+
console.log(` ${check(health.serverHealthy)} Server ${health.serverHealthy ? chalk7.green("healthy") + chalk7.dim(` (port ${config.httpPort})`) : chalk7.red("unhealthy")}`);
|
|
1569
|
+
console.log(` ${check(!!health.tunnelUrl)} Tunnel ${health.tunnelUrl ? chalk7.green(health.tunnelUrl) : chalk7.dim("not active")}`);
|
|
1421
1570
|
console.log("");
|
|
1422
1571
|
if (health.serverHealthy) {
|
|
1423
1572
|
log.success(`NEXUS CLI ready \u2014 connect via: buildwithnexus ssh`);
|
|
@@ -1426,7 +1575,7 @@ var statusCommand = new Command4("status").description("Check NEXUS runtime heal
|
|
|
1426
1575
|
|
|
1427
1576
|
// src/commands/doctor.ts
|
|
1428
1577
|
import { Command as Command5 } from "commander";
|
|
1429
|
-
import
|
|
1578
|
+
import chalk8 from "chalk";
|
|
1430
1579
|
import fs7 from "fs";
|
|
1431
1580
|
init_qemu();
|
|
1432
1581
|
init_secrets();
|
|
@@ -1434,12 +1583,12 @@ import path8 from "path";
|
|
|
1434
1583
|
import { execa as execa4 } from "execa";
|
|
1435
1584
|
var doctorCommand = new Command5("doctor").description("Diagnose NEXUS runtime environment").action(async () => {
|
|
1436
1585
|
const platform = detectPlatform();
|
|
1437
|
-
const check = (ok) => ok ?
|
|
1586
|
+
const check = (ok) => ok ? chalk8.green("\u2713") : chalk8.red("\u2717");
|
|
1438
1587
|
console.log("");
|
|
1439
|
-
console.log(
|
|
1588
|
+
console.log(chalk8.bold(" NEXUS Doctor"));
|
|
1440
1589
|
console.log("");
|
|
1441
1590
|
const nodeOk = Number(process.versions.node.split(".")[0]) >= 18;
|
|
1442
|
-
console.log(` ${check(nodeOk)} Node.js ${process.versions.node} ${nodeOk ? "" :
|
|
1591
|
+
console.log(` ${check(nodeOk)} Node.js ${process.versions.node} ${nodeOk ? "" : chalk8.red("(need >= 18)")}`);
|
|
1443
1592
|
console.log(` ${check(true)} Platform: ${platform.os} ${platform.arch}`);
|
|
1444
1593
|
const qemuOk = await isQemuInstalled(platform);
|
|
1445
1594
|
if (qemuOk) {
|
|
@@ -1479,14 +1628,14 @@ var doctorCommand = new Command5("doctor").description("Diagnose NEXUS runtime e
|
|
|
1479
1628
|
});
|
|
1480
1629
|
server.listen(port);
|
|
1481
1630
|
});
|
|
1482
|
-
console.log(` ${check(available)} Port ${port} (${name}) ${available ? "available" :
|
|
1631
|
+
console.log(` ${check(available)} Port ${port} (${name}) ${available ? "available" : chalk8.red("in use")}`);
|
|
1483
1632
|
} catch {
|
|
1484
1633
|
console.log(` ${check(false)} Port ${port} (${name}) \u2014 check failed`);
|
|
1485
1634
|
}
|
|
1486
1635
|
}
|
|
1487
1636
|
}
|
|
1488
1637
|
const biosOk = fs7.existsSync(platform.biosPath);
|
|
1489
|
-
console.log(` ${check(biosOk)} UEFI firmware ${biosOk ? "" :
|
|
1638
|
+
console.log(` ${check(biosOk)} UEFI firmware ${biosOk ? "" : chalk8.dim(platform.biosPath)}`);
|
|
1490
1639
|
console.log("");
|
|
1491
1640
|
if (qemuOk && isoTool && biosOk) {
|
|
1492
1641
|
log.success("Environment ready for NEXUS");
|
|
@@ -1597,7 +1746,7 @@ var updateCommand = new Command7("update").description("Update NEXUS to the late
|
|
|
1597
1746
|
|
|
1598
1747
|
// src/commands/destroy.ts
|
|
1599
1748
|
import { Command as Command8 } from "commander";
|
|
1600
|
-
import
|
|
1749
|
+
import chalk9 from "chalk";
|
|
1601
1750
|
import fs9 from "fs";
|
|
1602
1751
|
import { input as input2 } from "@inquirer/prompts";
|
|
1603
1752
|
init_secrets();
|
|
@@ -1607,11 +1756,11 @@ var destroyCommand = new Command8("destroy").description("Remove NEXUS VM and al
|
|
|
1607
1756
|
const config = loadConfig();
|
|
1608
1757
|
if (!opts.force) {
|
|
1609
1758
|
console.log("");
|
|
1610
|
-
console.log(
|
|
1611
|
-
console.log(
|
|
1612
|
-
console.log(
|
|
1613
|
-
console.log(
|
|
1614
|
-
console.log(
|
|
1759
|
+
console.log(chalk9.red.bold(" This will permanently delete:"));
|
|
1760
|
+
console.log(chalk9.red(" - NEXUS VM and all data inside it"));
|
|
1761
|
+
console.log(chalk9.red(" - VM disk images"));
|
|
1762
|
+
console.log(chalk9.red(" - SSH keys"));
|
|
1763
|
+
console.log(chalk9.red(" - Configuration and API keys"));
|
|
1615
1764
|
console.log("");
|
|
1616
1765
|
const confirm2 = await input2({
|
|
1617
1766
|
message: 'Type "destroy" to confirm:'
|
|
@@ -1655,7 +1804,7 @@ var destroyCommand = new Command8("destroy").description("Remove NEXUS VM and al
|
|
|
1655
1804
|
// src/commands/keys.ts
|
|
1656
1805
|
import { Command as Command9 } from "commander";
|
|
1657
1806
|
import { password as password2 } from "@inquirer/prompts";
|
|
1658
|
-
import
|
|
1807
|
+
import chalk10 from "chalk";
|
|
1659
1808
|
init_secrets();
|
|
1660
1809
|
init_dlp();
|
|
1661
1810
|
var keysCommand = new Command9("keys").description("Manage API keys");
|
|
@@ -1665,10 +1814,10 @@ keysCommand.command("list").description("Show configured API keys (masked)").act
|
|
|
1665
1814
|
log.error("No keys configured. Run: buildwithnexus init");
|
|
1666
1815
|
process.exit(1);
|
|
1667
1816
|
}
|
|
1668
|
-
console.log(
|
|
1817
|
+
console.log(chalk10.bold("\n Configured Keys\n"));
|
|
1669
1818
|
for (const [name, value] of Object.entries(keys)) {
|
|
1670
1819
|
if (value) {
|
|
1671
|
-
console.log(` ${
|
|
1820
|
+
console.log(` ${chalk10.cyan(name.padEnd(24))} ${maskKey(value)}`);
|
|
1672
1821
|
}
|
|
1673
1822
|
}
|
|
1674
1823
|
console.log("");
|
|
@@ -1733,18 +1882,18 @@ var sshCommand = new Command10("ssh").description("Open an SSH session into the
|
|
|
1733
1882
|
|
|
1734
1883
|
// src/commands/brainstorm.ts
|
|
1735
1884
|
import { Command as Command11 } from "commander";
|
|
1736
|
-
import
|
|
1885
|
+
import chalk11 from "chalk";
|
|
1737
1886
|
import { input as input3 } from "@inquirer/prompts";
|
|
1738
1887
|
init_secrets();
|
|
1739
1888
|
init_qemu();
|
|
1740
1889
|
init_ssh();
|
|
1741
1890
|
init_dlp();
|
|
1742
|
-
var COS_PREFIX =
|
|
1743
|
-
var YOU_PREFIX =
|
|
1744
|
-
var DIVIDER =
|
|
1891
|
+
var COS_PREFIX = chalk11.bold.cyan(" Chief of Staff");
|
|
1892
|
+
var YOU_PREFIX = chalk11.bold.white(" You");
|
|
1893
|
+
var DIVIDER = chalk11.dim(" " + "\u2500".repeat(56));
|
|
1745
1894
|
function formatResponse(text) {
|
|
1746
1895
|
const lines = text.split("\n");
|
|
1747
|
-
return lines.map((line) =>
|
|
1896
|
+
return lines.map((line) => chalk11.white(" " + line)).join("\n");
|
|
1748
1897
|
}
|
|
1749
1898
|
async function sendMessage(sshPort, message, source) {
|
|
1750
1899
|
const payload = JSON.stringify({ message, source });
|
|
@@ -1787,13 +1936,13 @@ var brainstormCommand = new Command11("brainstorm").description("Brainstorm an i
|
|
|
1787
1936
|
}
|
|
1788
1937
|
succeed(spinner, "Connected to NEXUS");
|
|
1789
1938
|
console.log("");
|
|
1790
|
-
console.log(
|
|
1791
|
-
console.log(
|
|
1792
|
-
console.log(
|
|
1793
|
-
console.log(
|
|
1794
|
-
console.log(
|
|
1795
|
-
console.log(
|
|
1796
|
-
console.log(
|
|
1939
|
+
console.log(chalk11.bold(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
1940
|
+
console.log(chalk11.bold(" \u2551 ") + chalk11.bold.cyan("NEXUS Brainstorm Session") + chalk11.bold(" \u2551"));
|
|
1941
|
+
console.log(chalk11.bold(" \u2560\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
|
|
1942
|
+
console.log(chalk11.bold(" \u2551 ") + chalk11.dim("The Chief of Staff will discuss your idea with the".padEnd(55)) + chalk11.bold("\u2551"));
|
|
1943
|
+
console.log(chalk11.bold(" \u2551 ") + chalk11.dim("NEXUS team and share their recommendations.".padEnd(55)) + chalk11.bold("\u2551"));
|
|
1944
|
+
console.log(chalk11.bold(" \u2551 ") + chalk11.dim("Type 'exit' or 'quit' to end the session.".padEnd(55)) + chalk11.bold("\u2551"));
|
|
1945
|
+
console.log(chalk11.bold(" \u255A\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
1797
1946
|
console.log("");
|
|
1798
1947
|
let idea = ideaWords.length > 0 ? ideaWords.join(" ") : "";
|
|
1799
1948
|
if (!idea) {
|
|
@@ -1810,7 +1959,7 @@ var brainstormCommand = new Command11("brainstorm").description("Brainstorm an i
|
|
|
1810
1959
|
while (true) {
|
|
1811
1960
|
turn++;
|
|
1812
1961
|
if (turn === 1) {
|
|
1813
|
-
console.log(`${YOU_PREFIX}: ${
|
|
1962
|
+
console.log(`${YOU_PREFIX}: ${chalk11.white(idea)}`);
|
|
1814
1963
|
}
|
|
1815
1964
|
console.log(DIVIDER);
|
|
1816
1965
|
const thinking = createSpinner(
|
|
@@ -1828,17 +1977,17 @@ var brainstormCommand = new Command11("brainstorm").description("Brainstorm an i
|
|
|
1828
1977
|
console.log(formatResponse(redact(response)));
|
|
1829
1978
|
console.log(DIVIDER);
|
|
1830
1979
|
const followUp = await input3({
|
|
1831
|
-
message:
|
|
1980
|
+
message: chalk11.bold("You:")
|
|
1832
1981
|
});
|
|
1833
1982
|
const trimmed = followUp.trim().toLowerCase();
|
|
1834
1983
|
if (!trimmed || trimmed === "exit" || trimmed === "quit" || trimmed === "q") {
|
|
1835
1984
|
console.log("");
|
|
1836
1985
|
log.success("Brainstorm session ended");
|
|
1837
|
-
console.log(
|
|
1986
|
+
console.log(chalk11.dim(" Run again anytime: buildwithnexus brainstorm"));
|
|
1838
1987
|
console.log("");
|
|
1839
1988
|
return;
|
|
1840
1989
|
}
|
|
1841
|
-
console.log(`${YOU_PREFIX}: ${
|
|
1990
|
+
console.log(`${YOU_PREFIX}: ${chalk11.white(followUp)}`);
|
|
1842
1991
|
currentMessage = `[BRAINSTORM FOLLOW-UP] The CEO responds: ${followUp}`;
|
|
1843
1992
|
}
|
|
1844
1993
|
} catch (err) {
|
|
@@ -1855,18 +2004,18 @@ var brainstormCommand = new Command11("brainstorm").description("Brainstorm an i
|
|
|
1855
2004
|
|
|
1856
2005
|
// src/commands/ninety-nine.ts
|
|
1857
2006
|
import { Command as Command12 } from "commander";
|
|
1858
|
-
import
|
|
2007
|
+
import chalk12 from "chalk";
|
|
1859
2008
|
import { input as input4 } from "@inquirer/prompts";
|
|
1860
2009
|
init_secrets();
|
|
1861
2010
|
init_qemu();
|
|
1862
2011
|
init_ssh();
|
|
1863
2012
|
init_dlp();
|
|
1864
|
-
var AGENT_PREFIX =
|
|
1865
|
-
var YOU_PREFIX2 =
|
|
1866
|
-
var DIVIDER2 =
|
|
2013
|
+
var AGENT_PREFIX = chalk12.bold.green(" 99 \u276F");
|
|
2014
|
+
var YOU_PREFIX2 = chalk12.bold.white(" You");
|
|
2015
|
+
var DIVIDER2 = chalk12.dim(" " + "\u2500".repeat(56));
|
|
1867
2016
|
function formatAgentActivity(text) {
|
|
1868
2017
|
const lines = text.split("\n");
|
|
1869
|
-
return lines.map((line) =>
|
|
2018
|
+
return lines.map((line) => chalk12.white(" " + line)).join("\n");
|
|
1870
2019
|
}
|
|
1871
2020
|
function parsePrefixes(instruction) {
|
|
1872
2021
|
const files = [];
|
|
@@ -1930,14 +2079,14 @@ var ninetyNineCommand = new Command12("99").description("AI pair-programming ses
|
|
|
1930
2079
|
}
|
|
1931
2080
|
succeed(spinner, "Connected to NEXUS");
|
|
1932
2081
|
console.log("");
|
|
1933
|
-
console.log(
|
|
1934
|
-
console.log(
|
|
1935
|
-
console.log(
|
|
1936
|
-
console.log(
|
|
1937
|
-
console.log(
|
|
1938
|
-
console.log(
|
|
1939
|
-
console.log(
|
|
1940
|
-
console.log(
|
|
2082
|
+
console.log(chalk12.bold(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
2083
|
+
console.log(chalk12.bold(" \u2551 ") + chalk12.bold.green("/99 Pair Programming") + chalk12.dim(" \u2014 powered by NEXUS") + chalk12.bold(" \u2551"));
|
|
2084
|
+
console.log(chalk12.bold(" \u2560\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
|
|
2085
|
+
console.log(chalk12.bold(" \u2551 ") + chalk12.dim("Describe what you want changed. NEXUS engineers".padEnd(55)) + chalk12.bold("\u2551"));
|
|
2086
|
+
console.log(chalk12.bold(" \u2551 ") + chalk12.dim("analyze and modify your code in real time.".padEnd(55)) + chalk12.bold("\u2551"));
|
|
2087
|
+
console.log(chalk12.bold(" \u2551 ") + chalk12.dim("Use @file to attach context, #rule to load rules.".padEnd(55)) + chalk12.bold("\u2551"));
|
|
2088
|
+
console.log(chalk12.bold(" \u2551 ") + chalk12.dim("Type 'exit' or 'quit' to end the session.".padEnd(55)) + chalk12.bold("\u2551"));
|
|
2089
|
+
console.log(chalk12.bold(" \u255A\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
1941
2090
|
console.log("");
|
|
1942
2091
|
const cwd = process.cwd();
|
|
1943
2092
|
if (opts.edit) {
|
|
@@ -1946,7 +2095,7 @@ var ninetyNineCommand = new Command12("99").description("AI pair-programming ses
|
|
|
1946
2095
|
});
|
|
1947
2096
|
const { cleaned, files, rules } = parsePrefixes(instruction);
|
|
1948
2097
|
const fullInstruction = `Edit file ${opts.edit}: ${cleaned}`;
|
|
1949
|
-
console.log(`${YOU_PREFIX2}: ${
|
|
2098
|
+
console.log(`${YOU_PREFIX2}: ${chalk12.white(fullInstruction)}`);
|
|
1950
2099
|
console.log(DIVIDER2);
|
|
1951
2100
|
const thinking = createSpinner("NEXUS engineers analyzing the file...");
|
|
1952
2101
|
thinking.start();
|
|
@@ -1960,7 +2109,7 @@ var ninetyNineCommand = new Command12("99").description("AI pair-programming ses
|
|
|
1960
2109
|
}
|
|
1961
2110
|
if (opts.search) {
|
|
1962
2111
|
const fullInstruction = `Search the codebase for: ${opts.search}`;
|
|
1963
|
-
console.log(`${YOU_PREFIX2}: ${
|
|
2112
|
+
console.log(`${YOU_PREFIX2}: ${chalk12.white(fullInstruction)}`);
|
|
1964
2113
|
console.log(DIVIDER2);
|
|
1965
2114
|
const thinking = createSpinner("Searching with NEXUS context...");
|
|
1966
2115
|
thinking.start();
|
|
@@ -1974,7 +2123,7 @@ var ninetyNineCommand = new Command12("99").description("AI pair-programming ses
|
|
|
1974
2123
|
}
|
|
1975
2124
|
if (opts.debug) {
|
|
1976
2125
|
const fullInstruction = "Debug the current issue \u2014 analyze recent errors, identify the root cause, and propose a fix";
|
|
1977
|
-
console.log(`${YOU_PREFIX2}: ${
|
|
2126
|
+
console.log(`${YOU_PREFIX2}: ${chalk12.white(fullInstruction)}`);
|
|
1978
2127
|
console.log(DIVIDER2);
|
|
1979
2128
|
const thinking = createSpinner("NEXUS debugger agent analyzing...");
|
|
1980
2129
|
thinking.start();
|
|
@@ -1989,7 +2138,7 @@ var ninetyNineCommand = new Command12("99").description("AI pair-programming ses
|
|
|
1989
2138
|
let initialInstruction = instructionWords.length > 0 ? instructionWords.join(" ") : "";
|
|
1990
2139
|
if (!initialInstruction) {
|
|
1991
2140
|
initialInstruction = await input4({
|
|
1992
|
-
message:
|
|
2141
|
+
message: chalk12.green("99 \u276F")
|
|
1993
2142
|
});
|
|
1994
2143
|
if (!initialInstruction.trim()) {
|
|
1995
2144
|
log.warn("No instruction provided");
|
|
@@ -2004,13 +2153,13 @@ var ninetyNineCommand = new Command12("99").description("AI pair-programming ses
|
|
|
2004
2153
|
const display = currentInstruction;
|
|
2005
2154
|
const nexusInstruction = cleaned || currentInstruction;
|
|
2006
2155
|
if (turn === 1) {
|
|
2007
|
-
console.log(`${YOU_PREFIX2}: ${
|
|
2156
|
+
console.log(`${YOU_PREFIX2}: ${chalk12.white(display)}`);
|
|
2008
2157
|
}
|
|
2009
2158
|
if (files.length > 0) {
|
|
2010
|
-
console.log(
|
|
2159
|
+
console.log(chalk12.dim(` Attaching: ${files.join(", ")}`));
|
|
2011
2160
|
}
|
|
2012
2161
|
if (rules.length > 0) {
|
|
2013
|
-
console.log(
|
|
2162
|
+
console.log(chalk12.dim(` Rules: ${rules.join(", ")}`));
|
|
2014
2163
|
}
|
|
2015
2164
|
console.log(DIVIDER2);
|
|
2016
2165
|
const thinking = createSpinner(
|
|
@@ -2024,17 +2173,17 @@ var ninetyNineCommand = new Command12("99").description("AI pair-programming ses
|
|
|
2024
2173
|
console.log(formatAgentActivity(redact(response)));
|
|
2025
2174
|
console.log(DIVIDER2);
|
|
2026
2175
|
const followUp = await input4({
|
|
2027
|
-
message:
|
|
2176
|
+
message: chalk12.green("99 \u276F")
|
|
2028
2177
|
});
|
|
2029
2178
|
const trimmed = followUp.trim().toLowerCase();
|
|
2030
2179
|
if (!trimmed || trimmed === "exit" || trimmed === "quit" || trimmed === "q") {
|
|
2031
2180
|
console.log("");
|
|
2032
2181
|
log.success("/99 session ended");
|
|
2033
|
-
console.log(
|
|
2182
|
+
console.log(chalk12.dim(" Run again: buildwithnexus 99"));
|
|
2034
2183
|
console.log("");
|
|
2035
2184
|
return;
|
|
2036
2185
|
}
|
|
2037
|
-
console.log(`${YOU_PREFIX2}: ${
|
|
2186
|
+
console.log(`${YOU_PREFIX2}: ${chalk12.white(followUp)}`);
|
|
2038
2187
|
currentInstruction = followUp;
|
|
2039
2188
|
}
|
|
2040
2189
|
} catch (err) {
|
|
@@ -2051,7 +2200,7 @@ var ninetyNineCommand = new Command12("99").description("AI pair-programming ses
|
|
|
2051
2200
|
|
|
2052
2201
|
// src/commands/shell.ts
|
|
2053
2202
|
import { Command as Command13 } from "commander";
|
|
2054
|
-
import
|
|
2203
|
+
import chalk15 from "chalk";
|
|
2055
2204
|
init_secrets();
|
|
2056
2205
|
init_qemu();
|
|
2057
2206
|
init_ssh();
|
|
@@ -2062,7 +2211,7 @@ init_secrets();
|
|
|
2062
2211
|
import readline from "readline";
|
|
2063
2212
|
import fs10 from "fs";
|
|
2064
2213
|
import path11 from "path";
|
|
2065
|
-
import
|
|
2214
|
+
import chalk13 from "chalk";
|
|
2066
2215
|
var HISTORY_FILE = path11.join(NEXUS_HOME2, "shell_history");
|
|
2067
2216
|
var MAX_HISTORY = 1e3;
|
|
2068
2217
|
var Repl = class {
|
|
@@ -2097,7 +2246,7 @@ var Repl = class {
|
|
|
2097
2246
|
this.rl = readline.createInterface({
|
|
2098
2247
|
input: process.stdin,
|
|
2099
2248
|
output: process.stdout,
|
|
2100
|
-
prompt:
|
|
2249
|
+
prompt: chalk13.bold.cyan("nexus") + chalk13.dim(" \u276F "),
|
|
2101
2250
|
history: this.history,
|
|
2102
2251
|
historySize: MAX_HISTORY,
|
|
2103
2252
|
terminal: true
|
|
@@ -2127,25 +2276,25 @@ var Repl = class {
|
|
|
2127
2276
|
try {
|
|
2128
2277
|
await cmd.handler();
|
|
2129
2278
|
} catch (err) {
|
|
2130
|
-
console.log(
|
|
2279
|
+
console.log(chalk13.red(` \u2717 Command failed: ${err.message}`));
|
|
2131
2280
|
}
|
|
2132
2281
|
this.rl?.prompt();
|
|
2133
2282
|
return;
|
|
2134
2283
|
}
|
|
2135
|
-
console.log(
|
|
2284
|
+
console.log(chalk13.yellow(` Unknown command: /${cmdName}. Type /help for available commands.`));
|
|
2136
2285
|
this.rl?.prompt();
|
|
2137
2286
|
return;
|
|
2138
2287
|
}
|
|
2139
2288
|
try {
|
|
2140
2289
|
await this.onMessage(trimmed);
|
|
2141
2290
|
} catch (err) {
|
|
2142
|
-
console.log(
|
|
2291
|
+
console.log(chalk13.red(` \u2717 ${err.message}`));
|
|
2143
2292
|
}
|
|
2144
2293
|
this.rl?.prompt();
|
|
2145
2294
|
});
|
|
2146
2295
|
this.rl.on("close", () => {
|
|
2147
2296
|
this.saveHistory();
|
|
2148
|
-
console.log(
|
|
2297
|
+
console.log(chalk13.dim("\n Session ended."));
|
|
2149
2298
|
process.exit(0);
|
|
2150
2299
|
});
|
|
2151
2300
|
this.rl.on("SIGINT", () => {
|
|
@@ -2154,15 +2303,15 @@ var Repl = class {
|
|
|
2154
2303
|
}
|
|
2155
2304
|
showHelp() {
|
|
2156
2305
|
console.log("");
|
|
2157
|
-
console.log(
|
|
2158
|
-
console.log(
|
|
2306
|
+
console.log(chalk13.bold(" Available Commands:"));
|
|
2307
|
+
console.log(chalk13.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"));
|
|
2159
2308
|
for (const [name, cmd] of this.commands) {
|
|
2160
|
-
console.log(` ${
|
|
2309
|
+
console.log(` ${chalk13.cyan("/" + name.padEnd(14))} ${chalk13.dim(cmd.description)}`);
|
|
2161
2310
|
}
|
|
2162
|
-
console.log(` ${
|
|
2163
|
-
console.log(` ${
|
|
2164
|
-
console.log(
|
|
2165
|
-
console.log(
|
|
2311
|
+
console.log(` ${chalk13.cyan("/help".padEnd(15))} ${chalk13.dim("Show this help message")}`);
|
|
2312
|
+
console.log(` ${chalk13.cyan("/exit".padEnd(15))} ${chalk13.dim("Exit the shell")}`);
|
|
2313
|
+
console.log(chalk13.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"));
|
|
2314
|
+
console.log(chalk13.dim(" Type anything else to chat with NEXUS"));
|
|
2166
2315
|
console.log("");
|
|
2167
2316
|
}
|
|
2168
2317
|
write(text) {
|
|
@@ -2188,17 +2337,17 @@ var Repl = class {
|
|
|
2188
2337
|
init_ssh();
|
|
2189
2338
|
init_secrets();
|
|
2190
2339
|
init_dlp();
|
|
2191
|
-
import
|
|
2340
|
+
import chalk14 from "chalk";
|
|
2192
2341
|
var ROLE_COLORS = {
|
|
2193
|
-
"Chief of Staff":
|
|
2194
|
-
"VP Engineering":
|
|
2195
|
-
"VP Product":
|
|
2196
|
-
"Senior Engineer":
|
|
2197
|
-
"Engineer":
|
|
2198
|
-
"QA Lead":
|
|
2199
|
-
"Security Engineer":
|
|
2200
|
-
"DevOps Engineer":
|
|
2201
|
-
"default":
|
|
2342
|
+
"Chief of Staff": chalk14.bold.cyan,
|
|
2343
|
+
"VP Engineering": chalk14.bold.blue,
|
|
2344
|
+
"VP Product": chalk14.bold.magenta,
|
|
2345
|
+
"Senior Engineer": chalk14.bold.green,
|
|
2346
|
+
"Engineer": chalk14.green,
|
|
2347
|
+
"QA Lead": chalk14.bold.yellow,
|
|
2348
|
+
"Security Engineer": chalk14.bold.red,
|
|
2349
|
+
"DevOps Engineer": chalk14.bold.hex("#FF8C00"),
|
|
2350
|
+
"default": chalk14.bold.white
|
|
2202
2351
|
};
|
|
2203
2352
|
function getColor(role) {
|
|
2204
2353
|
if (!role) return ROLE_COLORS["default"];
|
|
@@ -2209,15 +2358,15 @@ function formatEvent(event) {
|
|
|
2209
2358
|
const prefix = event.role ? color(` [${event.role}]`) : "";
|
|
2210
2359
|
switch (event.type) {
|
|
2211
2360
|
case "agent_thinking":
|
|
2212
|
-
return `${prefix} ${
|
|
2361
|
+
return `${prefix} ${chalk14.dim("thinking...")}`;
|
|
2213
2362
|
case "agent_response":
|
|
2214
2363
|
return `${prefix} ${redact(event.content ?? "")}`;
|
|
2215
2364
|
case "task_delegated":
|
|
2216
|
-
return `${prefix} ${
|
|
2365
|
+
return `${prefix} ${chalk14.dim("\u2192")} delegated to ${chalk14.bold(event.target ?? "agent")}`;
|
|
2217
2366
|
case "agent_complete":
|
|
2218
|
-
return `${prefix} ${
|
|
2367
|
+
return `${prefix} ${chalk14.green("\u2713")} ${chalk14.dim("complete")}`;
|
|
2219
2368
|
case "error":
|
|
2220
|
-
return ` ${
|
|
2369
|
+
return ` ${chalk14.red("\u2717")} ${redact(event.content ?? "Unknown error")}`;
|
|
2221
2370
|
case "heartbeat":
|
|
2222
2371
|
return null;
|
|
2223
2372
|
default:
|
|
@@ -2300,19 +2449,19 @@ async function sendMessage2(sshPort, message) {
|
|
|
2300
2449
|
}
|
|
2301
2450
|
}
|
|
2302
2451
|
function showShellBanner(health) {
|
|
2303
|
-
const check =
|
|
2304
|
-
const cross =
|
|
2452
|
+
const check = chalk15.green("\u2713");
|
|
2453
|
+
const cross = chalk15.red("\u2717");
|
|
2305
2454
|
console.log("");
|
|
2306
|
-
console.log(
|
|
2307
|
-
console.log(
|
|
2308
|
-
console.log(
|
|
2309
|
-
console.log(
|
|
2455
|
+
console.log(chalk15.bold(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
2456
|
+
console.log(chalk15.bold(" \u2551 ") + chalk15.bold.cyan("NEXUS Interactive Shell") + chalk15.bold(" \u2551"));
|
|
2457
|
+
console.log(chalk15.bold(" \u2560\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
|
|
2458
|
+
console.log(chalk15.bold(" \u2551 ") + `${health.vmRunning ? check : cross} VM ${health.sshReady ? check : cross} SSH ${health.dockerReady ? check : cross} Docker ${health.serverHealthy ? check : cross} Engine`.padEnd(55) + chalk15.bold("\u2551"));
|
|
2310
2459
|
if (health.tunnelUrl) {
|
|
2311
|
-
console.log(
|
|
2460
|
+
console.log(chalk15.bold(" \u2551 ") + chalk15.dim(`Tunnel: ${health.tunnelUrl}`.padEnd(55)) + chalk15.bold("\u2551"));
|
|
2312
2461
|
}
|
|
2313
|
-
console.log(
|
|
2314
|
-
console.log(
|
|
2315
|
-
console.log(
|
|
2462
|
+
console.log(chalk15.bold(" \u2560\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
|
|
2463
|
+
console.log(chalk15.bold(" \u2551 ") + chalk15.dim("Type naturally to chat \xB7 /help for commands".padEnd(55)) + chalk15.bold("\u2551"));
|
|
2464
|
+
console.log(chalk15.bold(" \u255A\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
2316
2465
|
console.log("");
|
|
2317
2466
|
}
|
|
2318
2467
|
async function getAgentList(sshPort) {
|
|
@@ -2322,13 +2471,13 @@ async function getAgentList(sshPort) {
|
|
|
2322
2471
|
const agents = JSON.parse(stdout);
|
|
2323
2472
|
if (!Array.isArray(agents)) return stdout;
|
|
2324
2473
|
const lines = [""];
|
|
2325
|
-
lines.push(
|
|
2326
|
-
lines.push(
|
|
2474
|
+
lines.push(chalk15.bold(" Registered Agents:"));
|
|
2475
|
+
lines.push(chalk15.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"));
|
|
2327
2476
|
for (const agent of agents) {
|
|
2328
2477
|
const name = agent.name ?? agent.id ?? "unknown";
|
|
2329
2478
|
const role = agent.role ?? "";
|
|
2330
|
-
const status = agent.status === "active" ?
|
|
2331
|
-
lines.push(` ${status} ${
|
|
2479
|
+
const status = agent.status === "active" ? chalk15.green("\u25CF") : chalk15.dim("\u25CB");
|
|
2480
|
+
lines.push(` ${status} ${chalk15.bold(name.padEnd(24))} ${chalk15.dim(role)}`);
|
|
2332
2481
|
}
|
|
2333
2482
|
lines.push("");
|
|
2334
2483
|
return lines.join("\n");
|
|
@@ -2340,11 +2489,11 @@ async function getStatus(sshPort) {
|
|
|
2340
2489
|
try {
|
|
2341
2490
|
const vmRunning = isVmRunning();
|
|
2342
2491
|
const health = await checkHealth(sshPort, vmRunning);
|
|
2343
|
-
const check =
|
|
2344
|
-
const cross =
|
|
2492
|
+
const check = chalk15.green("\u2713");
|
|
2493
|
+
const cross = chalk15.red("\u2717");
|
|
2345
2494
|
const lines = [""];
|
|
2346
|
-
lines.push(
|
|
2347
|
-
lines.push(
|
|
2495
|
+
lines.push(chalk15.bold(" System Status:"));
|
|
2496
|
+
lines.push(chalk15.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"));
|
|
2348
2497
|
lines.push(` ${health.vmRunning ? check : cross} Virtual Machine`);
|
|
2349
2498
|
lines.push(` ${health.sshReady ? check : cross} SSH Connection`);
|
|
2350
2499
|
lines.push(` ${health.dockerReady ? check : cross} Docker Engine`);
|
|
@@ -2366,17 +2515,17 @@ async function getCost(sshPort) {
|
|
|
2366
2515
|
if (code !== 0) return " Could not retrieve cost data";
|
|
2367
2516
|
const data = JSON.parse(stdout);
|
|
2368
2517
|
const lines = [""];
|
|
2369
|
-
lines.push(
|
|
2370
|
-
lines.push(
|
|
2518
|
+
lines.push(chalk15.bold(" Token Costs:"));
|
|
2519
|
+
lines.push(chalk15.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"));
|
|
2371
2520
|
if (data.total !== void 0) {
|
|
2372
|
-
lines.push(` Total: ${
|
|
2521
|
+
lines.push(` Total: ${chalk15.bold.green("$" + Number(data.total).toFixed(4))}`);
|
|
2373
2522
|
}
|
|
2374
2523
|
if (data.today !== void 0) {
|
|
2375
|
-
lines.push(` Today: ${
|
|
2524
|
+
lines.push(` Today: ${chalk15.bold("$" + Number(data.today).toFixed(4))}`);
|
|
2376
2525
|
}
|
|
2377
2526
|
if (data.by_agent && typeof data.by_agent === "object") {
|
|
2378
2527
|
lines.push("");
|
|
2379
|
-
lines.push(
|
|
2528
|
+
lines.push(chalk15.dim(" By Agent:"));
|
|
2380
2529
|
for (const [agent, cost] of Object.entries(data.by_agent)) {
|
|
2381
2530
|
lines.push(` ${agent.padEnd(20)} $${Number(cost).toFixed(4)}`);
|
|
2382
2531
|
}
|
|
@@ -2421,10 +2570,10 @@ var shellCommand2 = new Command13("shell").description("Launch the interactive N
|
|
|
2421
2570
|
thinkingSpinner.stop();
|
|
2422
2571
|
thinkingSpinner.clear();
|
|
2423
2572
|
console.log("");
|
|
2424
|
-
console.log(
|
|
2573
|
+
console.log(chalk15.bold.cyan(" Chief of Staff:"));
|
|
2425
2574
|
const lines = redact(response).split("\n");
|
|
2426
2575
|
for (const line of lines) {
|
|
2427
|
-
console.log(
|
|
2576
|
+
console.log(chalk15.white(" " + line));
|
|
2428
2577
|
}
|
|
2429
2578
|
console.log("");
|
|
2430
2579
|
} catch (err) {
|
|
@@ -2438,14 +2587,14 @@ var shellCommand2 = new Command13("shell").description("Launch the interactive N
|
|
|
2438
2587
|
description: "Brainstorm with the full NEXUS org (led by Chief of Staff)",
|
|
2439
2588
|
handler: async () => {
|
|
2440
2589
|
console.log("");
|
|
2441
|
-
console.log(
|
|
2442
|
-
console.log(
|
|
2443
|
-
console.log(
|
|
2444
|
-
console.log(
|
|
2445
|
-
console.log(
|
|
2446
|
-
console.log(
|
|
2447
|
-
console.log(
|
|
2448
|
-
console.log(
|
|
2590
|
+
console.log(chalk15.bold(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
2591
|
+
console.log(chalk15.bold(" \u2551 ") + chalk15.bold.cyan("NEXUS Brainstorm Session") + chalk15.bold(" \u2551"));
|
|
2592
|
+
console.log(chalk15.bold(" \u2560\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
|
|
2593
|
+
console.log(chalk15.bold(" \u2551 ") + chalk15.dim("The Chief of Staff will facilitate a discussion with".padEnd(55)) + chalk15.bold("\u2551"));
|
|
2594
|
+
console.log(chalk15.bold(" \u2551 ") + chalk15.dim("the full NEXUS org to refine your idea. When ready,".padEnd(55)) + chalk15.bold("\u2551"));
|
|
2595
|
+
console.log(chalk15.bold(" \u2551 ") + chalk15.dim("NEXUS will draft an execution plan for your review.".padEnd(55)) + chalk15.bold("\u2551"));
|
|
2596
|
+
console.log(chalk15.bold(" \u2551 ") + chalk15.dim("Type 'exit' to end brainstorm. Type 'plan' to hand off.".padEnd(55)) + chalk15.bold("\u2551"));
|
|
2597
|
+
console.log(chalk15.bold(" \u255A\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
2449
2598
|
console.log("");
|
|
2450
2599
|
const { input: input5 } = await import("@inquirer/prompts");
|
|
2451
2600
|
const idea = await input5({ message: "What would you like to brainstorm?" });
|
|
@@ -2458,12 +2607,12 @@ var shellCommand2 = new Command13("shell").description("Launch the interactive N
|
|
|
2458
2607
|
brainstormSpinner.stop();
|
|
2459
2608
|
brainstormSpinner.clear();
|
|
2460
2609
|
console.log("");
|
|
2461
|
-
console.log(
|
|
2610
|
+
console.log(chalk15.bold.cyan(" Chief of Staff:"));
|
|
2462
2611
|
for (const line of redact(response).split("\n")) {
|
|
2463
|
-
console.log(
|
|
2612
|
+
console.log(chalk15.white(" " + line));
|
|
2464
2613
|
}
|
|
2465
2614
|
console.log("");
|
|
2466
|
-
const followUp = await input5({ message:
|
|
2615
|
+
const followUp = await input5({ message: chalk15.bold("You:") });
|
|
2467
2616
|
const trimmed = followUp.trim().toLowerCase();
|
|
2468
2617
|
if (!trimmed || trimmed === "exit" || trimmed === "quit") {
|
|
2469
2618
|
console.log("");
|
|
@@ -2477,9 +2626,9 @@ var shellCommand2 = new Command13("shell").description("Launch the interactive N
|
|
|
2477
2626
|
planSpinner.stop();
|
|
2478
2627
|
planSpinner.clear();
|
|
2479
2628
|
console.log("");
|
|
2480
|
-
console.log(
|
|
2629
|
+
console.log(chalk15.bold.green(" Execution Plan:"));
|
|
2481
2630
|
for (const line of redact(planResponse).split("\n")) {
|
|
2482
|
-
console.log(
|
|
2631
|
+
console.log(chalk15.white(" " + line));
|
|
2483
2632
|
}
|
|
2484
2633
|
console.log("");
|
|
2485
2634
|
log.success("Plan drafted. Use the shell to refine or approve.");
|
|
@@ -2519,10 +2668,10 @@ var shellCommand2 = new Command13("shell").description("Launch the interactive N
|
|
|
2519
2668
|
handler: async () => {
|
|
2520
2669
|
const { stdout } = await sshExec(config.sshPort, "tail -30 /home/nexus/.nexus/logs/server.log 2>/dev/null");
|
|
2521
2670
|
console.log("");
|
|
2522
|
-
console.log(
|
|
2523
|
-
console.log(
|
|
2671
|
+
console.log(chalk15.bold(" Recent Logs:"));
|
|
2672
|
+
console.log(chalk15.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"));
|
|
2524
2673
|
for (const line of redact(stdout).split("\n")) {
|
|
2525
|
-
console.log(
|
|
2674
|
+
console.log(chalk15.dim(" " + line));
|
|
2526
2675
|
}
|
|
2527
2676
|
console.log("");
|
|
2528
2677
|
}
|
|
@@ -2542,24 +2691,24 @@ var shellCommand2 = new Command13("shell").description("Launch the interactive N
|
|
|
2542
2691
|
description: "Display the NEXUS organizational hierarchy",
|
|
2543
2692
|
handler: async () => {
|
|
2544
2693
|
console.log("");
|
|
2545
|
-
console.log(
|
|
2546
|
-
console.log(
|
|
2547
|
-
console.log(` ${
|
|
2548
|
-
console.log(` \u2514\u2500\u2500 ${
|
|
2549
|
-
console.log(` \u251C\u2500\u2500 ${
|
|
2550
|
-
console.log(` \u2502 \u251C\u2500\u2500 ${
|
|
2551
|
-
console.log(` \u2502 \u251C\u2500\u2500 ${
|
|
2552
|
-
console.log(` \u2502 \u2514\u2500\u2500 ${
|
|
2553
|
-
console.log(` \u251C\u2500\u2500 ${
|
|
2554
|
-
console.log(` \u2502 \u251C\u2500\u2500 ${
|
|
2555
|
-
console.log(` \u2502 \u2514\u2500\u2500 ${
|
|
2556
|
-
console.log(` \u251C\u2500\u2500 ${
|
|
2557
|
-
console.log(` \u2502 \u2514\u2500\u2500 ${
|
|
2558
|
-
console.log(` \u251C\u2500\u2500 ${
|
|
2559
|
-
console.log(` \u2514\u2500\u2500 ${
|
|
2694
|
+
console.log(chalk15.bold(" NEXUS Organizational Hierarchy"));
|
|
2695
|
+
console.log(chalk15.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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2696
|
+
console.log(` ${chalk15.bold.white("You")} ${chalk15.dim("(CEO)")}`);
|
|
2697
|
+
console.log(` \u2514\u2500\u2500 ${chalk15.bold.cyan("Chief of Staff")} ${chalk15.dim("\u2014 orchestrates all work, your direct interface")}`);
|
|
2698
|
+
console.log(` \u251C\u2500\u2500 ${chalk15.bold.blue("VP Engineering")} ${chalk15.dim("\u2014 owns technical execution")}`);
|
|
2699
|
+
console.log(` \u2502 \u251C\u2500\u2500 ${chalk15.green("Senior Engineer")} ${chalk15.dim("\xD7 8 \u2014 implementation, refactoring")}`);
|
|
2700
|
+
console.log(` \u2502 \u251C\u2500\u2500 ${chalk15.green("Engineer")} ${chalk15.dim("\xD7 12 \u2014 feature work, bug fixes")}`);
|
|
2701
|
+
console.log(` \u2502 \u2514\u2500\u2500 ${chalk15.hex("#FF8C00")("DevOps Engineer")} ${chalk15.dim("\xD7 4 \u2014 CI/CD, Docker, infra")}`);
|
|
2702
|
+
console.log(` \u251C\u2500\u2500 ${chalk15.bold.magenta("VP Product")} ${chalk15.dim("\u2014 owns roadmap and priorities")}`);
|
|
2703
|
+
console.log(` \u2502 \u251C\u2500\u2500 ${chalk15.magenta("Product Manager")} ${chalk15.dim("\xD7 3 \u2014 specs, requirements")}`);
|
|
2704
|
+
console.log(` \u2502 \u2514\u2500\u2500 ${chalk15.magenta("Designer")} ${chalk15.dim("\xD7 2 \u2014 UI/UX, prototyping")}`);
|
|
2705
|
+
console.log(` \u251C\u2500\u2500 ${chalk15.bold.yellow("QA Lead")} ${chalk15.dim("\u2014 owns quality assurance")}`);
|
|
2706
|
+
console.log(` \u2502 \u2514\u2500\u2500 ${chalk15.yellow("QA Engineer")} ${chalk15.dim("\xD7 6 \u2014 testing, coverage, validation")}`);
|
|
2707
|
+
console.log(` \u251C\u2500\u2500 ${chalk15.bold.red("Security Engineer")} ${chalk15.dim("\xD7 4 \u2014 auth, scanning, compliance")}`);
|
|
2708
|
+
console.log(` \u2514\u2500\u2500 ${chalk15.bold.white("Knowledge Manager")} ${chalk15.dim("\u2014 RAG, documentation, learning")}`);
|
|
2560
2709
|
console.log("");
|
|
2561
|
-
console.log(
|
|
2562
|
-
console.log(
|
|
2710
|
+
console.log(chalk15.dim(" 56 agents total \xB7 Self-learning ML pipeline"));
|
|
2711
|
+
console.log(chalk15.dim(" Full details: https://buildwithnexus.dev/overview"));
|
|
2563
2712
|
console.log("");
|
|
2564
2713
|
}
|
|
2565
2714
|
});
|
|
@@ -2648,14 +2797,14 @@ var shellCommand2 = new Command13("shell").description("Launch the interactive N
|
|
|
2648
2797
|
for (let i = 0; i < steps.length; i++) {
|
|
2649
2798
|
const step = steps[i];
|
|
2650
2799
|
console.log("");
|
|
2651
|
-
console.log(
|
|
2800
|
+
console.log(chalk15.bold(` \u2500\u2500 ${chalk15.cyan(`Step ${i + 1}/${steps.length}`)} \u2500\u2500 ${step.title} \u2500\u2500`));
|
|
2652
2801
|
console.log("");
|
|
2653
2802
|
for (const line of step.content) {
|
|
2654
|
-
console.log(
|
|
2803
|
+
console.log(chalk15.white(" " + line));
|
|
2655
2804
|
}
|
|
2656
2805
|
console.log("");
|
|
2657
2806
|
if (i < steps.length - 1) {
|
|
2658
|
-
const next = await input5({ message:
|
|
2807
|
+
const next = await input5({ message: chalk15.dim("Press Enter to continue (or 'skip' to exit)") });
|
|
2659
2808
|
if (next.trim().toLowerCase() === "skip") {
|
|
2660
2809
|
log.success("Tutorial ended. Type /help to see all commands.");
|
|
2661
2810
|
return;
|
|
@@ -2702,9 +2851,9 @@ cli.addCommand(shellCommand2);
|
|
|
2702
2851
|
cli.action(async () => {
|
|
2703
2852
|
try {
|
|
2704
2853
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_secrets(), secrets_exports));
|
|
2705
|
-
const { isVmRunning:
|
|
2854
|
+
const { isVmRunning: isVmRunning2 } = await Promise.resolve().then(() => (init_qemu(), qemu_exports));
|
|
2706
2855
|
const config = loadConfig2();
|
|
2707
|
-
if (config &&
|
|
2856
|
+
if (config && isVmRunning2()) {
|
|
2708
2857
|
await shellCommand2.parseAsync([], { from: "user" });
|
|
2709
2858
|
return;
|
|
2710
2859
|
}
|
|
@@ -2718,7 +2867,7 @@ import fs11 from "fs";
|
|
|
2718
2867
|
import path12 from "path";
|
|
2719
2868
|
import os3 from "os";
|
|
2720
2869
|
import https from "https";
|
|
2721
|
-
import
|
|
2870
|
+
import chalk16 from "chalk";
|
|
2722
2871
|
var PACKAGE_NAME = "buildwithnexus";
|
|
2723
2872
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
2724
2873
|
var STATE_DIR = path12.join(os3.homedir(), ".buildwithnexus");
|
|
@@ -2799,8 +2948,8 @@ async function checkForUpdates(currentVersion) {
|
|
|
2799
2948
|
function printUpdateBanner(current, latest) {
|
|
2800
2949
|
const msg = [
|
|
2801
2950
|
"",
|
|
2802
|
-
|
|
2803
|
-
|
|
2951
|
+
chalk16.yellow(` Update available: ${current} \u2192 ${latest}`),
|
|
2952
|
+
chalk16.cyan(` Run: npm update -g buildwithnexus`),
|
|
2804
2953
|
""
|
|
2805
2954
|
].join("\n");
|
|
2806
2955
|
process.stderr.write(msg + "\n");
|