buildwithnexus 0.3.0 → 0.3.2
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 +917 -410
- package/dist/nexus-release.tar.gz +0 -0
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -9,6 +9,84 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/ui/banner.ts
|
|
13
|
+
var banner_exports = {};
|
|
14
|
+
__export(banner_exports, {
|
|
15
|
+
showBanner: () => showBanner,
|
|
16
|
+
showCompletion: () => showCompletion,
|
|
17
|
+
showPhase: () => showPhase,
|
|
18
|
+
showSecurityPosture: () => showSecurityPosture
|
|
19
|
+
});
|
|
20
|
+
import chalk from "chalk";
|
|
21
|
+
function showBanner() {
|
|
22
|
+
console.log(BANNER);
|
|
23
|
+
console.log(chalk.dim(" v0.3.1 \xB7 buildwithnexus.dev\n"));
|
|
24
|
+
}
|
|
25
|
+
function showPhase(phase, total, description) {
|
|
26
|
+
const progress = chalk.cyan(`[${phase}/${total}]`);
|
|
27
|
+
console.log(`
|
|
28
|
+
${progress} ${chalk.bold(description)}`);
|
|
29
|
+
}
|
|
30
|
+
function showSecurityPosture() {
|
|
31
|
+
const lines = [
|
|
32
|
+
"",
|
|
33
|
+
chalk.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"),
|
|
34
|
+
chalk.bold(" \u2551 ") + chalk.bold.green("Security Posture") + chalk.bold(" \u2551"),
|
|
35
|
+
chalk.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"),
|
|
36
|
+
chalk.bold(" \u2551 ") + chalk.green("\u2713") + chalk.white(" Triple-nested isolation: Host \u2192 VM \u2192 Docker \u2192 KVM".padEnd(54)) + chalk.bold("\u2551"),
|
|
37
|
+
chalk.bold(" \u2551 ") + chalk.green("\u2713") + chalk.white(" Network hardened: UFW deny-all, allow 22/80/443/4200".padEnd(54)) + chalk.bold("\u2551"),
|
|
38
|
+
chalk.bold(" \u2551 ") + chalk.green("\u2713") + chalk.white(" All databases encrypted at rest (AES-256-CBC)".padEnd(54)) + chalk.bold("\u2551"),
|
|
39
|
+
chalk.bold(" \u2551 ") + chalk.green("\u2713") + chalk.white(" API keys never embedded in VM \u2014 delivered via SCP".padEnd(54)) + chalk.bold("\u2551"),
|
|
40
|
+
chalk.bold(" \u2551 ") + chalk.green("\u2713") + chalk.white(" SSH-only communication (no exposed network ports)".padEnd(54)) + chalk.bold("\u2551"),
|
|
41
|
+
chalk.bold(" \u2551 ") + chalk.green("\u2713") + chalk.white(" DLP: secret detection, shell escaping, output redaction".padEnd(54)) + chalk.bold("\u2551"),
|
|
42
|
+
chalk.bold(" \u2551 ") + chalk.green("\u2713") + chalk.white(" HMAC integrity verification on all key files".padEnd(54)) + chalk.bold("\u2551"),
|
|
43
|
+
chalk.bold(" \u2551 ") + chalk.green("\u2713") + chalk.white(" Docker: --read-only, no-new-privileges, cap-drop=ALL".padEnd(54)) + chalk.bold("\u2551"),
|
|
44
|
+
chalk.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"),
|
|
45
|
+
chalk.bold(" \u2551 ") + chalk.dim("Full details: https://buildwithnexus.dev/security".padEnd(55)) + chalk.bold("\u2551"),
|
|
46
|
+
chalk.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"),
|
|
47
|
+
""
|
|
48
|
+
];
|
|
49
|
+
console.log(lines.join("\n"));
|
|
50
|
+
}
|
|
51
|
+
function showCompletion(urls) {
|
|
52
|
+
const lines = [
|
|
53
|
+
"",
|
|
54
|
+
chalk.green(" \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"),
|
|
55
|
+
chalk.green(" \u2551 ") + chalk.bold.green("NEXUS Runtime is Live!") + chalk.green(" \u2551"),
|
|
56
|
+
chalk.green(" \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"),
|
|
57
|
+
chalk.green(" \u2551 ") + chalk.white(`Connect: ${urls.ssh}`.padEnd(55)) + chalk.green("\u2551")
|
|
58
|
+
];
|
|
59
|
+
if (urls.remote) {
|
|
60
|
+
lines.push(chalk.green(" \u2551 ") + chalk.white(`Remote: ${urls.remote}`.padEnd(55)) + chalk.green("\u2551"));
|
|
61
|
+
}
|
|
62
|
+
lines.push(
|
|
63
|
+
chalk.green(" \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"),
|
|
64
|
+
chalk.green(" \u2551 ") + chalk.dim("Quick Start:".padEnd(55)) + chalk.green("\u2551"),
|
|
65
|
+
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus - Interactive shell".padEnd(55)) + chalk.green("\u2551"),
|
|
66
|
+
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus brainstorm - Brainstorm ideas".padEnd(55)) + chalk.green("\u2551"),
|
|
67
|
+
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus status - Check health".padEnd(55)) + chalk.green("\u2551"),
|
|
68
|
+
chalk.green(" \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"),
|
|
69
|
+
chalk.green(" \u2551 ") + chalk.dim("All commands:".padEnd(55)) + chalk.green("\u2551"),
|
|
70
|
+
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus stop/start/update/logs/ssh/destroy".padEnd(55)) + chalk.green("\u2551"),
|
|
71
|
+
chalk.green(" \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"),
|
|
72
|
+
""
|
|
73
|
+
);
|
|
74
|
+
console.log(lines.join("\n"));
|
|
75
|
+
}
|
|
76
|
+
var BANNER;
|
|
77
|
+
var init_banner = __esm({
|
|
78
|
+
"src/ui/banner.ts"() {
|
|
79
|
+
"use strict";
|
|
80
|
+
BANNER = `
|
|
81
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
82
|
+
\u2551 ${chalk.bold.cyan("B U I L D W I T H N E X U S")} \u2551
|
|
83
|
+
\u2551 \u2551
|
|
84
|
+
\u2551 Autonomous AI Runtime \xB7 Nested Isolation \u2551
|
|
85
|
+
\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\u255D
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
12
90
|
// src/core/dlp.ts
|
|
13
91
|
import crypto from "crypto";
|
|
14
92
|
import fs from "fs";
|
|
@@ -285,7 +363,7 @@ __export(qemu_exports, {
|
|
|
285
363
|
import fs3 from "fs";
|
|
286
364
|
import net from "net";
|
|
287
365
|
import path3 from "path";
|
|
288
|
-
import { execa } from "execa";
|
|
366
|
+
import { execa, execaSync } from "execa";
|
|
289
367
|
import { select } from "@inquirer/prompts";
|
|
290
368
|
import chalk5 from "chalk";
|
|
291
369
|
async function isQemuInstalled(platform) {
|
|
@@ -300,10 +378,16 @@ async function installQemu(platform) {
|
|
|
300
378
|
if (platform.os === "mac") {
|
|
301
379
|
await execa("brew", ["install", "qemu", "cdrtools"], { stdio: "inherit", env: scrubEnv() });
|
|
302
380
|
} else if (platform.os === "linux") {
|
|
381
|
+
let hasApt = false;
|
|
303
382
|
try {
|
|
383
|
+
await execa("which", ["apt-get"], { env: scrubEnv() });
|
|
384
|
+
hasApt = true;
|
|
385
|
+
} catch {
|
|
386
|
+
}
|
|
387
|
+
if (hasApt) {
|
|
304
388
|
await execa("sudo", ["apt-get", "update"], { stdio: "inherit", env: scrubEnv() });
|
|
305
389
|
await execa("sudo", ["apt-get", "install", "-y", "qemu-system", "qemu-utils", "genisoimage"], { stdio: "inherit", env: scrubEnv() });
|
|
306
|
-
}
|
|
390
|
+
} else {
|
|
307
391
|
await execa("sudo", ["yum", "install", "-y", "qemu-system-arm", "qemu-system-x86", "qemu-img", "genisoimage"], { stdio: "inherit", env: scrubEnv() });
|
|
308
392
|
}
|
|
309
393
|
} else {
|
|
@@ -314,7 +398,7 @@ async function downloadImage(platform) {
|
|
|
314
398
|
const imagePath = path3.join(IMAGES_DIR, platform.ubuntuImage);
|
|
315
399
|
if (fs3.existsSync(imagePath)) return imagePath;
|
|
316
400
|
const url = `${UBUNTU_BASE_URL}/${platform.ubuntuImage}`;
|
|
317
|
-
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() });
|
|
318
402
|
return imagePath;
|
|
319
403
|
}
|
|
320
404
|
async function createDisk(basePath, sizeGb) {
|
|
@@ -412,38 +496,36 @@ async function resolvePortConflicts(ports) {
|
|
|
412
496
|
return resolved;
|
|
413
497
|
}
|
|
414
498
|
async function launchVm(platform, diskPath, initIsoPath, ram, cpus, ports) {
|
|
415
|
-
const
|
|
499
|
+
const machineArgs = platform.os === "mac" ? ["-machine", "virt,gic-version=3"] : ["-machine", "pc"];
|
|
416
500
|
const biosArgs = fs3.existsSync(platform.biosPath) ? ["-bios", platform.biosPath] : [];
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
];
|
|
442
|
-
}
|
|
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
|
+
];
|
|
443
525
|
try {
|
|
444
|
-
await execa(platform.qemuBinary, buildArgs(platform.qemuCpuFlag), { env: scrubEnv() });
|
|
526
|
+
await execa(platform.qemuBinary, buildArgs(platform.qemuCpuFlag.split(" ")), { env: scrubEnv() });
|
|
445
527
|
} catch {
|
|
446
|
-
const fallbackCpu = platform.os === "mac" ? "-cpu max" : "-cpu qemu64";
|
|
528
|
+
const fallbackCpu = platform.os === "mac" ? ["-cpu", "max"] : ["-cpu", "qemu64"];
|
|
447
529
|
await execa(platform.qemuBinary, buildArgs(fallbackCpu), { env: scrubEnv() });
|
|
448
530
|
}
|
|
449
531
|
return ports;
|
|
@@ -455,6 +537,14 @@ function readValidPid() {
|
|
|
455
537
|
if (!Number.isInteger(pid) || pid <= 1 || pid > 4194304) return null;
|
|
456
538
|
return pid;
|
|
457
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
|
+
}
|
|
458
548
|
function isVmRunning() {
|
|
459
549
|
const pid = readValidPid();
|
|
460
550
|
if (!pid) return false;
|
|
@@ -468,6 +558,13 @@ function isVmRunning() {
|
|
|
468
558
|
function stopVm() {
|
|
469
559
|
const pid = readValidPid();
|
|
470
560
|
if (!pid) return;
|
|
561
|
+
if (!isQemuPid(pid)) {
|
|
562
|
+
try {
|
|
563
|
+
fs3.unlinkSync(PID_FILE);
|
|
564
|
+
} catch {
|
|
565
|
+
}
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
471
568
|
try {
|
|
472
569
|
process.kill(pid, "SIGTERM");
|
|
473
570
|
} catch {
|
|
@@ -639,56 +736,12 @@ var init_ssh = __esm({
|
|
|
639
736
|
});
|
|
640
737
|
|
|
641
738
|
// src/cli.ts
|
|
642
|
-
import { Command as
|
|
739
|
+
import { Command as Command14 } from "commander";
|
|
643
740
|
|
|
644
741
|
// src/commands/init.ts
|
|
742
|
+
init_banner();
|
|
645
743
|
import { Command } from "commander";
|
|
646
|
-
|
|
647
|
-
// src/ui/banner.ts
|
|
648
|
-
import chalk from "chalk";
|
|
649
|
-
var BANNER = `
|
|
650
|
-
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
651
|
-
\u2551 ${chalk.bold.cyan("B U I L D W I T H N E X U S")} \u2551
|
|
652
|
-
\u2551 \u2551
|
|
653
|
-
\u2551 Autonomous AI Runtime \xB7 Nested Isolation \u2551
|
|
654
|
-
\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\u255D
|
|
655
|
-
`;
|
|
656
|
-
function showBanner() {
|
|
657
|
-
console.log(BANNER);
|
|
658
|
-
console.log(chalk.dim(" v0.2.8 \xB7 buildwithnexus.dev\n"));
|
|
659
|
-
}
|
|
660
|
-
function showPhase(phase, total, description) {
|
|
661
|
-
const progress = chalk.cyan(`[${phase}/${total}]`);
|
|
662
|
-
console.log(`
|
|
663
|
-
${progress} ${chalk.bold(description)}`);
|
|
664
|
-
}
|
|
665
|
-
function showCompletion(urls) {
|
|
666
|
-
const lines = [
|
|
667
|
-
"",
|
|
668
|
-
chalk.green(" \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"),
|
|
669
|
-
chalk.green(" \u2551 ") + chalk.bold.green("NEXUS Runtime is Live!") + chalk.green(" \u2551"),
|
|
670
|
-
chalk.green(" \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"),
|
|
671
|
-
chalk.green(" \u2551 ") + chalk.white(`Connect: ${urls.ssh}`.padEnd(55)) + chalk.green("\u2551")
|
|
672
|
-
];
|
|
673
|
-
if (urls.remote) {
|
|
674
|
-
lines.push(chalk.green(" \u2551 ") + chalk.white(`Remote: ${urls.remote}`.padEnd(55)) + chalk.green("\u2551"));
|
|
675
|
-
}
|
|
676
|
-
lines.push(
|
|
677
|
-
chalk.green(" \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"),
|
|
678
|
-
chalk.green(" \u2551 ") + chalk.dim("Commands:".padEnd(55)) + chalk.green("\u2551"),
|
|
679
|
-
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus ssh - Open CLI".padEnd(55)) + chalk.green("\u2551"),
|
|
680
|
-
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus status - Check health".padEnd(55)) + chalk.green("\u2551"),
|
|
681
|
-
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus logs - View logs".padEnd(55)) + chalk.green("\u2551"),
|
|
682
|
-
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus stop - Shutdown".padEnd(55)) + chalk.green("\u2551"),
|
|
683
|
-
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus start - Restart".padEnd(55)) + chalk.green("\u2551"),
|
|
684
|
-
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus update - Update release".padEnd(55)) + chalk.green("\u2551"),
|
|
685
|
-
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus brainstorm - Brainstorm ideas".padEnd(55)) + chalk.green("\u2551"),
|
|
686
|
-
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus destroy - Remove all".padEnd(55)) + chalk.green("\u2551"),
|
|
687
|
-
chalk.green(" \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"),
|
|
688
|
-
""
|
|
689
|
-
);
|
|
690
|
-
console.log(lines.join("\n"));
|
|
691
|
-
}
|
|
744
|
+
import chalk6 from "chalk";
|
|
692
745
|
|
|
693
746
|
// src/ui/spinner.ts
|
|
694
747
|
import ora from "ora";
|
|
@@ -895,69 +948,65 @@ async function createCloudInitIso(userDataPath) {
|
|
|
895
948
|
fs5.writeFileSync(metaDataPath, "instance-id: nexus-vm-1\nlocal-hostname: nexus-vm\n", { mode: 384 });
|
|
896
949
|
const isoPath = path5.join(IMAGES_DIR2, "init.iso");
|
|
897
950
|
const env = scrubEnv();
|
|
898
|
-
let created = false;
|
|
899
951
|
try {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
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 {
|
|
914
1000
|
try {
|
|
915
|
-
|
|
916
|
-
"-output",
|
|
917
|
-
isoPath,
|
|
918
|
-
"-volid",
|
|
919
|
-
"cidata",
|
|
920
|
-
"-joliet",
|
|
921
|
-
"-rock",
|
|
922
|
-
userDataPath,
|
|
923
|
-
metaDataPath
|
|
924
|
-
], { env });
|
|
925
|
-
created = true;
|
|
1001
|
+
fs5.unlinkSync(userDataPath);
|
|
926
1002
|
} catch {
|
|
927
1003
|
}
|
|
928
|
-
}
|
|
929
|
-
if (!created) {
|
|
930
1004
|
try {
|
|
931
|
-
|
|
932
|
-
fs5.mkdirSync(stagingDir, { recursive: true, mode: 448 });
|
|
933
|
-
fs5.copyFileSync(userDataPath, path5.join(stagingDir, "user-data"));
|
|
934
|
-
fs5.copyFileSync(metaDataPath, path5.join(stagingDir, "meta-data"));
|
|
935
|
-
await execa3("hdiutil", [
|
|
936
|
-
"makehybrid",
|
|
937
|
-
"-o",
|
|
938
|
-
isoPath,
|
|
939
|
-
"-joliet",
|
|
940
|
-
"-iso",
|
|
941
|
-
"-default-volume-name",
|
|
942
|
-
"cidata",
|
|
943
|
-
stagingDir
|
|
944
|
-
], { env });
|
|
945
|
-
fs5.rmSync(stagingDir, { recursive: true, force: true });
|
|
946
|
-
created = true;
|
|
1005
|
+
fs5.unlinkSync(metaDataPath);
|
|
947
1006
|
} catch {
|
|
948
1007
|
}
|
|
1008
|
+
audit("cloudinit_plaintext_deleted", "user-data.yaml and meta-data.yaml removed");
|
|
949
1009
|
}
|
|
950
|
-
if (!created) {
|
|
951
|
-
throw new Error(
|
|
952
|
-
"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"
|
|
953
|
-
);
|
|
954
|
-
}
|
|
955
|
-
fs5.chmodSync(isoPath, 384);
|
|
956
|
-
fs5.unlinkSync(userDataPath);
|
|
957
|
-
fs5.unlinkSync(metaDataPath);
|
|
958
|
-
audit("cloudinit_plaintext_deleted", "user-data.yaml and meta-data.yaml removed");
|
|
959
|
-
audit("cloudinit_iso_created", "init.iso created");
|
|
960
|
-
return isoPath;
|
|
961
1010
|
}
|
|
962
1011
|
|
|
963
1012
|
// src/core/health.ts
|
|
@@ -996,27 +1045,51 @@ async function checkHealth(port, vmRunning) {
|
|
|
996
1045
|
}
|
|
997
1046
|
return status;
|
|
998
1047
|
}
|
|
999
|
-
async function waitForServer(port, timeoutMs =
|
|
1048
|
+
async function waitForServer(port, timeoutMs = 9e5) {
|
|
1000
1049
|
const start = Date.now();
|
|
1050
|
+
let lastLog = 0;
|
|
1001
1051
|
while (Date.now() - start < timeoutMs) {
|
|
1002
1052
|
try {
|
|
1003
1053
|
const { stdout, code } = await sshExec(port, "curl -sf http://localhost:4200/health");
|
|
1004
1054
|
if (code === 0 && stdout.includes("ok")) return true;
|
|
1005
1055
|
} catch {
|
|
1006
1056
|
}
|
|
1007
|
-
|
|
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));
|
|
1008
1069
|
}
|
|
1009
1070
|
return false;
|
|
1010
1071
|
}
|
|
1011
|
-
async function waitForCloudInit(port, timeoutMs =
|
|
1072
|
+
async function waitForCloudInit(port, timeoutMs = 18e5) {
|
|
1012
1073
|
const start = Date.now();
|
|
1074
|
+
let lastLog = 0;
|
|
1013
1075
|
while (Date.now() - start < timeoutMs) {
|
|
1014
1076
|
try {
|
|
1015
1077
|
const { code } = await sshExec(port, "test -f /var/lib/cloud/instance/boot-finished");
|
|
1016
1078
|
if (code === 0) return true;
|
|
1017
1079
|
} catch {
|
|
1018
1080
|
}
|
|
1019
|
-
|
|
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));
|
|
1020
1093
|
}
|
|
1021
1094
|
return false;
|
|
1022
1095
|
}
|
|
@@ -1043,17 +1116,19 @@ async function installCloudflared(sshPort, arch) {
|
|
|
1043
1116
|
}
|
|
1044
1117
|
async function startTunnel(sshPort) {
|
|
1045
1118
|
await sshExec(sshPort, [
|
|
1046
|
-
"
|
|
1047
|
-
"
|
|
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 &",
|
|
1048
1122
|
"disown"
|
|
1049
1123
|
].join(" "));
|
|
1050
1124
|
const start = Date.now();
|
|
1051
|
-
while (Date.now() - start <
|
|
1125
|
+
while (Date.now() - start < 6e4) {
|
|
1052
1126
|
try {
|
|
1053
|
-
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");
|
|
1054
1128
|
if (stdout.includes("https://")) {
|
|
1055
1129
|
const url = stdout.trim();
|
|
1056
1130
|
if (!/^https:\/\/[a-z0-9-]+\.trycloudflare\.com$/.test(url)) {
|
|
1131
|
+
audit("tunnel_url_rejected", `Invalid URL format: ${url.slice(0, 80)}`);
|
|
1057
1132
|
return null;
|
|
1058
1133
|
}
|
|
1059
1134
|
await sshExec(sshPort, shellCommand`printf '%s\n' ${url} > /home/nexus/.nexus/tunnel-url.txt && chmod 600 /home/nexus/.nexus/tunnel-url.txt`);
|
|
@@ -1086,176 +1161,281 @@ function getReleaseTarball() {
|
|
|
1086
1161
|
if (fs6.existsSync(rootPath)) return rootPath;
|
|
1087
1162
|
throw new Error("nexus-release.tar.gz not found. Run: npm run bundle");
|
|
1088
1163
|
}
|
|
1089
|
-
var TOTAL_PHASES = 10;
|
|
1090
1164
|
function getCloudInitTemplate() {
|
|
1091
1165
|
const dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
1092
1166
|
const templatePath = path6.join(dir, "templates", "cloud-init.yaml.ejs");
|
|
1093
|
-
if (fs6.existsSync(templatePath))
|
|
1094
|
-
return fs6.readFileSync(templatePath, "utf-8");
|
|
1095
|
-
}
|
|
1167
|
+
if (fs6.existsSync(templatePath)) return fs6.readFileSync(templatePath, "utf-8");
|
|
1096
1168
|
const srcPath = path6.resolve(dir, "..", "src", "templates", "cloud-init.yaml.ejs");
|
|
1097
1169
|
return fs6.readFileSync(srcPath, "utf-8");
|
|
1098
1170
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
masterSecret
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
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;
|
|
1129
1217
|
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
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)));
|
|
1143
1232
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
succeed(spinner, "Ubuntu image ready");
|
|
1155
|
-
showPhase(5, TOTAL_PHASES, "Cloud-Init Generation");
|
|
1156
|
-
spinner = createSpinner("Locating release tarball...");
|
|
1157
|
-
spinner.start();
|
|
1158
|
-
const tarballPath = getReleaseTarball();
|
|
1159
|
-
succeed(spinner, `Release tarball found (${path6.basename(tarballPath)})`);
|
|
1160
|
-
spinner = createSpinner("Rendering cloud-init...");
|
|
1161
|
-
spinner.start();
|
|
1162
|
-
const pubKey = getPubKey();
|
|
1163
|
-
const template = getCloudInitTemplate();
|
|
1164
|
-
const userDataPath = await renderCloudInit({ sshPubKey: pubKey, keys, config }, template);
|
|
1165
|
-
const isoPath = await createCloudInitIso(userDataPath);
|
|
1166
|
-
succeed(spinner, "Cloud-init ISO created");
|
|
1167
|
-
showPhase(6, TOTAL_PHASES, "VM Launch");
|
|
1168
|
-
spinner = createSpinner("Checking port availability...");
|
|
1169
|
-
spinner.start();
|
|
1170
|
-
const requestedPorts = { ssh: config.sshPort, http: config.httpPort, https: config.httpsPort };
|
|
1171
|
-
spinner.stop();
|
|
1172
|
-
spinner.clear();
|
|
1173
|
-
const resolvedPorts = await resolvePortConflicts(requestedPorts);
|
|
1174
|
-
spinner = createSpinner("Creating disk and launching VM...");
|
|
1175
|
-
spinner.start();
|
|
1176
|
-
const diskPath = await createDisk(imagePath, config.vmDisk);
|
|
1177
|
-
await launchVm(platform, diskPath, isoPath, config.vmRam, config.vmCpus, resolvedPorts);
|
|
1178
|
-
config.sshPort = resolvedPorts.ssh;
|
|
1179
|
-
config.httpPort = resolvedPorts.http;
|
|
1180
|
-
config.httpsPort = resolvedPorts.https;
|
|
1181
|
-
saveConfig(config);
|
|
1182
|
-
const portNote = resolvedPorts.ssh !== 2222 || resolvedPorts.http !== 4200 || resolvedPorts.https !== 8443 ? ` (ports: SSH=${resolvedPorts.ssh}, HTTP=${resolvedPorts.http}, HTTPS=${resolvedPorts.https})` : "";
|
|
1183
|
-
succeed(spinner, `VM launched (daemonized)${portNote}`);
|
|
1184
|
-
showPhase(7, TOTAL_PHASES, "VM Provisioning");
|
|
1185
|
-
spinner = createSpinner("Waiting for SSH...");
|
|
1186
|
-
spinner.start();
|
|
1187
|
-
const sshReady = await waitForSsh(config.sshPort);
|
|
1188
|
-
if (!sshReady) {
|
|
1189
|
-
fail(spinner, "SSH connection timed out");
|
|
1190
|
-
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
|
+
});
|
|
1191
1243
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
await sshExec(config.sshPort, "chmod 600 /tmp/.nexus-env-keys");
|
|
1205
|
-
} finally {
|
|
1206
|
-
try {
|
|
1207
|
-
fs6.writeFileSync(tmpKeysPath, "0".repeat(keysContent.length));
|
|
1208
|
-
fs6.unlinkSync(tmpKeysPath);
|
|
1209
|
-
} catch {
|
|
1210
|
-
}
|
|
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;
|
|
1211
1256
|
}
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
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;
|
|
1220
1274
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
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;
|
|
1234
1301
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1302
|
+
},
|
|
1303
|
+
// Phase 7 — VM Provisioning
|
|
1304
|
+
{
|
|
1305
|
+
name: "VM Provisioning",
|
|
1306
|
+
run: async (ctx, spinner) => {
|
|
1307
|
+
const { config, keys, tarballPath } = ctx;
|
|
1308
|
+
spinner.text = "Waiting for SSH...";
|
|
1309
|
+
spinner.start();
|
|
1310
|
+
const sshReady = await waitForSsh(config.sshPort);
|
|
1311
|
+
if (!sshReady) {
|
|
1312
|
+
fail(spinner, "SSH connection timed out");
|
|
1313
|
+
throw new Error("SSH connection timed out");
|
|
1314
|
+
}
|
|
1315
|
+
succeed(spinner, "SSH connected");
|
|
1316
|
+
await withSpinner(
|
|
1317
|
+
spinner,
|
|
1318
|
+
"Uploading NEXUS release tarball...",
|
|
1319
|
+
() => sshUploadFile(config.sshPort, tarballPath, "/tmp/nexus-release.tar.gz")
|
|
1320
|
+
);
|
|
1321
|
+
await withSpinner(spinner, "Staging API keys...", async () => {
|
|
1322
|
+
const keysContent = Object.entries(keys).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
|
|
1323
|
+
const tmpKeysPath = path6.join(os2.tmpdir(), `.nexus-keys-${crypto4.randomBytes(8).toString("hex")}`);
|
|
1324
|
+
fs6.writeFileSync(tmpKeysPath, keysContent, { mode: 384 });
|
|
1325
|
+
try {
|
|
1326
|
+
await sshUploadFile(config.sshPort, tmpKeysPath, "/tmp/.nexus-env-keys");
|
|
1327
|
+
await sshExec(config.sshPort, "chmod 600 /tmp/.nexus-env-keys");
|
|
1328
|
+
} finally {
|
|
1329
|
+
try {
|
|
1330
|
+
fs6.writeFileSync(tmpKeysPath, "0".repeat(keysContent.length));
|
|
1331
|
+
fs6.unlinkSync(tmpKeysPath);
|
|
1332
|
+
} catch {
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
});
|
|
1336
|
+
spinner.text = "Cloud-init provisioning \u2014 this takes 10-20 min (extracting NEXUS, building Docker, installing deps)...";
|
|
1337
|
+
spinner.start();
|
|
1338
|
+
const cloudInitDone = await waitForCloudInit(config.sshPort);
|
|
1339
|
+
if (!cloudInitDone) {
|
|
1340
|
+
fail(spinner, "Cloud-init timed out after 30 minutes");
|
|
1341
|
+
log.warn("Check progress: buildwithnexus ssh \u2192 tail -f /var/log/cloud-init-output.log");
|
|
1342
|
+
throw new Error("Cloud-init timed out");
|
|
1343
|
+
}
|
|
1344
|
+
succeed(spinner, "VM fully provisioned");
|
|
1345
|
+
await withSpinner(
|
|
1346
|
+
spinner,
|
|
1347
|
+
"Delivering API keys...",
|
|
1348
|
+
() => sshExec(
|
|
1349
|
+
config.sshPort,
|
|
1350
|
+
"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"
|
|
1351
|
+
)
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
},
|
|
1355
|
+
// Phase 8 — Server Health
|
|
1356
|
+
{
|
|
1357
|
+
name: "NEXUS Server Startup",
|
|
1358
|
+
run: async (ctx, spinner) => {
|
|
1359
|
+
const { config } = ctx;
|
|
1360
|
+
spinner.text = "Waiting for NEXUS server...";
|
|
1240
1361
|
spinner.start();
|
|
1241
|
-
await
|
|
1362
|
+
const serverReady = await waitForServer(config.sshPort);
|
|
1363
|
+
if (!serverReady) {
|
|
1364
|
+
fail(spinner, "Server failed to start");
|
|
1365
|
+
log.warn("Check logs: buildwithnexus logs");
|
|
1366
|
+
throw new Error("NEXUS server failed to start");
|
|
1367
|
+
}
|
|
1368
|
+
succeed(spinner, "NEXUS server healthy on port 4200");
|
|
1369
|
+
}
|
|
1370
|
+
},
|
|
1371
|
+
// Phase 9 — Cloudflare Tunnel
|
|
1372
|
+
{
|
|
1373
|
+
name: "Cloudflare Tunnel",
|
|
1374
|
+
run: async (ctx, spinner) => {
|
|
1375
|
+
const { config, platform } = ctx;
|
|
1376
|
+
if (!config.enableTunnel) {
|
|
1377
|
+
log.dim("Skipped (not enabled)");
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
await withSpinner(
|
|
1381
|
+
spinner,
|
|
1382
|
+
"Installing cloudflared...",
|
|
1383
|
+
() => installCloudflared(config.sshPort, platform.arch)
|
|
1384
|
+
);
|
|
1242
1385
|
spinner.text = "Starting tunnel...";
|
|
1386
|
+
spinner.start();
|
|
1243
1387
|
const url = await startTunnel(config.sshPort);
|
|
1244
1388
|
if (url) {
|
|
1245
|
-
tunnelUrl = url;
|
|
1389
|
+
ctx.tunnelUrl = url;
|
|
1246
1390
|
succeed(spinner, `Tunnel active: ${url}`);
|
|
1247
1391
|
} else {
|
|
1248
1392
|
fail(spinner, "Tunnel failed to start (server still accessible locally)");
|
|
1249
1393
|
}
|
|
1250
|
-
} else {
|
|
1251
|
-
showPhase(9, TOTAL_PHASES, "Cloudflare Tunnel");
|
|
1252
|
-
log.dim("Skipped (not enabled)");
|
|
1253
1394
|
}
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1395
|
+
},
|
|
1396
|
+
// Phase 10 — Complete
|
|
1397
|
+
{
|
|
1398
|
+
name: "Complete",
|
|
1399
|
+
run: async (ctx) => {
|
|
1400
|
+
showCompletion({ remote: ctx.tunnelUrl, ssh: "buildwithnexus ssh" });
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
];
|
|
1404
|
+
function hasQemuLabel(installed) {
|
|
1405
|
+
return installed ? "QEMU ready" : "QEMU installed";
|
|
1406
|
+
}
|
|
1407
|
+
var TOTAL_PHASES = phases.length;
|
|
1408
|
+
async function runInit() {
|
|
1409
|
+
const ctx = { vmLaunched: false, tunnelUrl: void 0 };
|
|
1410
|
+
const spinner = createSpinner("");
|
|
1411
|
+
for (let i = 0; i < phases.length; i++) {
|
|
1412
|
+
const phase = phases[i];
|
|
1413
|
+
showPhase(i + 1, TOTAL_PHASES, phase.name);
|
|
1414
|
+
if (phase.skip?.(ctx)) {
|
|
1415
|
+
log.dim("Skipped");
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1418
|
+
try {
|
|
1419
|
+
await phase.run(ctx, spinner);
|
|
1420
|
+
} catch (err) {
|
|
1421
|
+
try {
|
|
1422
|
+
spinner.stop();
|
|
1423
|
+
} catch {
|
|
1424
|
+
}
|
|
1425
|
+
if (ctx.vmLaunched) {
|
|
1426
|
+
process.stderr.write(chalk6.dim("\n Stopping VM due to init failure...\n"));
|
|
1427
|
+
try {
|
|
1428
|
+
stopVm();
|
|
1429
|
+
} catch {
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
throw err;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
var initCommand = new Command("init").description("Scaffold and launch a new NEXUS runtime").action(async () => {
|
|
1437
|
+
try {
|
|
1438
|
+
await runInit();
|
|
1259
1439
|
} catch (err) {
|
|
1260
1440
|
const safeErr = redactError(err);
|
|
1261
1441
|
log.error(`Init failed: ${safeErr.message}`);
|
|
@@ -1361,7 +1541,7 @@ var stopCommand = new Command3("stop").description("Gracefully shut down the NEX
|
|
|
1361
1541
|
|
|
1362
1542
|
// src/commands/status.ts
|
|
1363
1543
|
import { Command as Command4 } from "commander";
|
|
1364
|
-
import
|
|
1544
|
+
import chalk7 from "chalk";
|
|
1365
1545
|
init_secrets();
|
|
1366
1546
|
init_qemu();
|
|
1367
1547
|
var statusCommand = new Command4("status").description("Check NEXUS runtime health").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -1376,15 +1556,15 @@ var statusCommand = new Command4("status").description("Check NEXUS runtime heal
|
|
|
1376
1556
|
console.log(JSON.stringify({ ...health, pid: getVmPid(), ports: { ssh: config.sshPort, http: config.httpPort, https: config.httpsPort } }, null, 2));
|
|
1377
1557
|
return;
|
|
1378
1558
|
}
|
|
1379
|
-
const check = (ok) => ok ?
|
|
1559
|
+
const check = (ok) => ok ? chalk7.green("\u25CF") : chalk7.red("\u25CB");
|
|
1380
1560
|
console.log("");
|
|
1381
|
-
console.log(
|
|
1561
|
+
console.log(chalk7.bold(" NEXUS Runtime Status"));
|
|
1382
1562
|
console.log("");
|
|
1383
|
-
console.log(` ${check(health.vmRunning)} VM ${health.vmRunning ?
|
|
1384
|
-
console.log(` ${check(health.sshReady)} SSH ${health.sshReady ?
|
|
1385
|
-
console.log(` ${check(health.dockerReady)} Docker ${health.dockerReady ?
|
|
1386
|
-
console.log(` ${check(health.serverHealthy)} Server ${health.serverHealthy ?
|
|
1387
|
-
console.log(` ${check(!!health.tunnelUrl)} Tunnel ${health.tunnelUrl ?
|
|
1563
|
+
console.log(` ${check(health.vmRunning)} VM ${health.vmRunning ? chalk7.green("running") + chalk7.dim(` (PID ${getVmPid()})`) : chalk7.red("stopped")}`);
|
|
1564
|
+
console.log(` ${check(health.sshReady)} SSH ${health.sshReady ? chalk7.green("connected") + chalk7.dim(` (port ${config.sshPort})`) : chalk7.red("unreachable")}`);
|
|
1565
|
+
console.log(` ${check(health.dockerReady)} Docker ${health.dockerReady ? chalk7.green("ready") : chalk7.red("not ready")}`);
|
|
1566
|
+
console.log(` ${check(health.serverHealthy)} Server ${health.serverHealthy ? chalk7.green("healthy") + chalk7.dim(` (port ${config.httpPort})`) : chalk7.red("unhealthy")}`);
|
|
1567
|
+
console.log(` ${check(!!health.tunnelUrl)} Tunnel ${health.tunnelUrl ? chalk7.green(health.tunnelUrl) : chalk7.dim("not active")}`);
|
|
1388
1568
|
console.log("");
|
|
1389
1569
|
if (health.serverHealthy) {
|
|
1390
1570
|
log.success(`NEXUS CLI ready \u2014 connect via: buildwithnexus ssh`);
|
|
@@ -1393,7 +1573,7 @@ var statusCommand = new Command4("status").description("Check NEXUS runtime heal
|
|
|
1393
1573
|
|
|
1394
1574
|
// src/commands/doctor.ts
|
|
1395
1575
|
import { Command as Command5 } from "commander";
|
|
1396
|
-
import
|
|
1576
|
+
import chalk8 from "chalk";
|
|
1397
1577
|
import fs7 from "fs";
|
|
1398
1578
|
init_qemu();
|
|
1399
1579
|
init_secrets();
|
|
@@ -1401,12 +1581,12 @@ import path8 from "path";
|
|
|
1401
1581
|
import { execa as execa4 } from "execa";
|
|
1402
1582
|
var doctorCommand = new Command5("doctor").description("Diagnose NEXUS runtime environment").action(async () => {
|
|
1403
1583
|
const platform = detectPlatform();
|
|
1404
|
-
const check = (ok) => ok ?
|
|
1584
|
+
const check = (ok) => ok ? chalk8.green("\u2713") : chalk8.red("\u2717");
|
|
1405
1585
|
console.log("");
|
|
1406
|
-
console.log(
|
|
1586
|
+
console.log(chalk8.bold(" NEXUS Doctor"));
|
|
1407
1587
|
console.log("");
|
|
1408
1588
|
const nodeOk = Number(process.versions.node.split(".")[0]) >= 18;
|
|
1409
|
-
console.log(` ${check(nodeOk)} Node.js ${process.versions.node} ${nodeOk ? "" :
|
|
1589
|
+
console.log(` ${check(nodeOk)} Node.js ${process.versions.node} ${nodeOk ? "" : chalk8.red("(need >= 18)")}`);
|
|
1410
1590
|
console.log(` ${check(true)} Platform: ${platform.os} ${platform.arch}`);
|
|
1411
1591
|
const qemuOk = await isQemuInstalled(platform);
|
|
1412
1592
|
if (qemuOk) {
|
|
@@ -1446,14 +1626,14 @@ var doctorCommand = new Command5("doctor").description("Diagnose NEXUS runtime e
|
|
|
1446
1626
|
});
|
|
1447
1627
|
server.listen(port);
|
|
1448
1628
|
});
|
|
1449
|
-
console.log(` ${check(available)} Port ${port} (${name}) ${available ? "available" :
|
|
1629
|
+
console.log(` ${check(available)} Port ${port} (${name}) ${available ? "available" : chalk8.red("in use")}`);
|
|
1450
1630
|
} catch {
|
|
1451
1631
|
console.log(` ${check(false)} Port ${port} (${name}) \u2014 check failed`);
|
|
1452
1632
|
}
|
|
1453
1633
|
}
|
|
1454
1634
|
}
|
|
1455
1635
|
const biosOk = fs7.existsSync(platform.biosPath);
|
|
1456
|
-
console.log(` ${check(biosOk)} UEFI firmware ${biosOk ? "" :
|
|
1636
|
+
console.log(` ${check(biosOk)} UEFI firmware ${biosOk ? "" : chalk8.dim(platform.biosPath)}`);
|
|
1457
1637
|
console.log("");
|
|
1458
1638
|
if (qemuOk && isoTool && biosOk) {
|
|
1459
1639
|
log.success("Environment ready for NEXUS");
|
|
@@ -1564,7 +1744,7 @@ var updateCommand = new Command7("update").description("Update NEXUS to the late
|
|
|
1564
1744
|
|
|
1565
1745
|
// src/commands/destroy.ts
|
|
1566
1746
|
import { Command as Command8 } from "commander";
|
|
1567
|
-
import
|
|
1747
|
+
import chalk9 from "chalk";
|
|
1568
1748
|
import fs9 from "fs";
|
|
1569
1749
|
import { input as input2 } from "@inquirer/prompts";
|
|
1570
1750
|
init_secrets();
|
|
@@ -1574,11 +1754,11 @@ var destroyCommand = new Command8("destroy").description("Remove NEXUS VM and al
|
|
|
1574
1754
|
const config = loadConfig();
|
|
1575
1755
|
if (!opts.force) {
|
|
1576
1756
|
console.log("");
|
|
1577
|
-
console.log(
|
|
1578
|
-
console.log(
|
|
1579
|
-
console.log(
|
|
1580
|
-
console.log(
|
|
1581
|
-
console.log(
|
|
1757
|
+
console.log(chalk9.red.bold(" This will permanently delete:"));
|
|
1758
|
+
console.log(chalk9.red(" - NEXUS VM and all data inside it"));
|
|
1759
|
+
console.log(chalk9.red(" - VM disk images"));
|
|
1760
|
+
console.log(chalk9.red(" - SSH keys"));
|
|
1761
|
+
console.log(chalk9.red(" - Configuration and API keys"));
|
|
1582
1762
|
console.log("");
|
|
1583
1763
|
const confirm2 = await input2({
|
|
1584
1764
|
message: 'Type "destroy" to confirm:'
|
|
@@ -1622,7 +1802,7 @@ var destroyCommand = new Command8("destroy").description("Remove NEXUS VM and al
|
|
|
1622
1802
|
// src/commands/keys.ts
|
|
1623
1803
|
import { Command as Command9 } from "commander";
|
|
1624
1804
|
import { password as password2 } from "@inquirer/prompts";
|
|
1625
|
-
import
|
|
1805
|
+
import chalk10 from "chalk";
|
|
1626
1806
|
init_secrets();
|
|
1627
1807
|
init_dlp();
|
|
1628
1808
|
var keysCommand = new Command9("keys").description("Manage API keys");
|
|
@@ -1632,10 +1812,10 @@ keysCommand.command("list").description("Show configured API keys (masked)").act
|
|
|
1632
1812
|
log.error("No keys configured. Run: buildwithnexus init");
|
|
1633
1813
|
process.exit(1);
|
|
1634
1814
|
}
|
|
1635
|
-
console.log(
|
|
1815
|
+
console.log(chalk10.bold("\n Configured Keys\n"));
|
|
1636
1816
|
for (const [name, value] of Object.entries(keys)) {
|
|
1637
1817
|
if (value) {
|
|
1638
|
-
console.log(` ${
|
|
1818
|
+
console.log(` ${chalk10.cyan(name.padEnd(24))} ${maskKey(value)}`);
|
|
1639
1819
|
}
|
|
1640
1820
|
}
|
|
1641
1821
|
console.log("");
|
|
@@ -1700,18 +1880,18 @@ var sshCommand = new Command10("ssh").description("Open an SSH session into the
|
|
|
1700
1880
|
|
|
1701
1881
|
// src/commands/brainstorm.ts
|
|
1702
1882
|
import { Command as Command11 } from "commander";
|
|
1703
|
-
import
|
|
1883
|
+
import chalk11 from "chalk";
|
|
1704
1884
|
import { input as input3 } from "@inquirer/prompts";
|
|
1705
1885
|
init_secrets();
|
|
1706
1886
|
init_qemu();
|
|
1707
1887
|
init_ssh();
|
|
1708
1888
|
init_dlp();
|
|
1709
|
-
var COS_PREFIX =
|
|
1710
|
-
var YOU_PREFIX =
|
|
1711
|
-
var DIVIDER =
|
|
1889
|
+
var COS_PREFIX = chalk11.bold.cyan(" Chief of Staff");
|
|
1890
|
+
var YOU_PREFIX = chalk11.bold.white(" You");
|
|
1891
|
+
var DIVIDER = chalk11.dim(" " + "\u2500".repeat(56));
|
|
1712
1892
|
function formatResponse(text) {
|
|
1713
1893
|
const lines = text.split("\n");
|
|
1714
|
-
return lines.map((line) =>
|
|
1894
|
+
return lines.map((line) => chalk11.white(" " + line)).join("\n");
|
|
1715
1895
|
}
|
|
1716
1896
|
async function sendMessage(sshPort, message, source) {
|
|
1717
1897
|
const payload = JSON.stringify({ message, source });
|
|
@@ -1754,13 +1934,13 @@ var brainstormCommand = new Command11("brainstorm").description("Brainstorm an i
|
|
|
1754
1934
|
}
|
|
1755
1935
|
succeed(spinner, "Connected to NEXUS");
|
|
1756
1936
|
console.log("");
|
|
1757
|
-
console.log(
|
|
1758
|
-
console.log(
|
|
1759
|
-
console.log(
|
|
1760
|
-
console.log(
|
|
1761
|
-
console.log(
|
|
1762
|
-
console.log(
|
|
1763
|
-
console.log(
|
|
1937
|
+
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"));
|
|
1938
|
+
console.log(chalk11.bold(" \u2551 ") + chalk11.bold.cyan("NEXUS Brainstorm Session") + chalk11.bold(" \u2551"));
|
|
1939
|
+
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"));
|
|
1940
|
+
console.log(chalk11.bold(" \u2551 ") + chalk11.dim("The Chief of Staff will discuss your idea with the".padEnd(55)) + chalk11.bold("\u2551"));
|
|
1941
|
+
console.log(chalk11.bold(" \u2551 ") + chalk11.dim("NEXUS team and share their recommendations.".padEnd(55)) + chalk11.bold("\u2551"));
|
|
1942
|
+
console.log(chalk11.bold(" \u2551 ") + chalk11.dim("Type 'exit' or 'quit' to end the session.".padEnd(55)) + chalk11.bold("\u2551"));
|
|
1943
|
+
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"));
|
|
1764
1944
|
console.log("");
|
|
1765
1945
|
let idea = ideaWords.length > 0 ? ideaWords.join(" ") : "";
|
|
1766
1946
|
if (!idea) {
|
|
@@ -1777,7 +1957,7 @@ var brainstormCommand = new Command11("brainstorm").description("Brainstorm an i
|
|
|
1777
1957
|
while (true) {
|
|
1778
1958
|
turn++;
|
|
1779
1959
|
if (turn === 1) {
|
|
1780
|
-
console.log(`${YOU_PREFIX}: ${
|
|
1960
|
+
console.log(`${YOU_PREFIX}: ${chalk11.white(idea)}`);
|
|
1781
1961
|
}
|
|
1782
1962
|
console.log(DIVIDER);
|
|
1783
1963
|
const thinking = createSpinner(
|
|
@@ -1795,17 +1975,17 @@ var brainstormCommand = new Command11("brainstorm").description("Brainstorm an i
|
|
|
1795
1975
|
console.log(formatResponse(redact(response)));
|
|
1796
1976
|
console.log(DIVIDER);
|
|
1797
1977
|
const followUp = await input3({
|
|
1798
|
-
message:
|
|
1978
|
+
message: chalk11.bold("You:")
|
|
1799
1979
|
});
|
|
1800
1980
|
const trimmed = followUp.trim().toLowerCase();
|
|
1801
1981
|
if (!trimmed || trimmed === "exit" || trimmed === "quit" || trimmed === "q") {
|
|
1802
1982
|
console.log("");
|
|
1803
1983
|
log.success("Brainstorm session ended");
|
|
1804
|
-
console.log(
|
|
1984
|
+
console.log(chalk11.dim(" Run again anytime: buildwithnexus brainstorm"));
|
|
1805
1985
|
console.log("");
|
|
1806
1986
|
return;
|
|
1807
1987
|
}
|
|
1808
|
-
console.log(`${YOU_PREFIX}: ${
|
|
1988
|
+
console.log(`${YOU_PREFIX}: ${chalk11.white(followUp)}`);
|
|
1809
1989
|
currentMessage = `[BRAINSTORM FOLLOW-UP] The CEO responds: ${followUp}`;
|
|
1810
1990
|
}
|
|
1811
1991
|
} catch (err) {
|
|
@@ -1820,9 +2000,205 @@ var brainstormCommand = new Command11("brainstorm").description("Brainstorm an i
|
|
|
1820
2000
|
}
|
|
1821
2001
|
});
|
|
1822
2002
|
|
|
1823
|
-
// src/commands/
|
|
2003
|
+
// src/commands/ninety-nine.ts
|
|
1824
2004
|
import { Command as Command12 } from "commander";
|
|
1825
|
-
import
|
|
2005
|
+
import chalk12 from "chalk";
|
|
2006
|
+
import { input as input4 } from "@inquirer/prompts";
|
|
2007
|
+
init_secrets();
|
|
2008
|
+
init_qemu();
|
|
2009
|
+
init_ssh();
|
|
2010
|
+
init_dlp();
|
|
2011
|
+
var AGENT_PREFIX = chalk12.bold.green(" 99 \u276F");
|
|
2012
|
+
var YOU_PREFIX2 = chalk12.bold.white(" You");
|
|
2013
|
+
var DIVIDER2 = chalk12.dim(" " + "\u2500".repeat(56));
|
|
2014
|
+
function formatAgentActivity(text) {
|
|
2015
|
+
const lines = text.split("\n");
|
|
2016
|
+
return lines.map((line) => chalk12.white(" " + line)).join("\n");
|
|
2017
|
+
}
|
|
2018
|
+
function parsePrefixes(instruction) {
|
|
2019
|
+
const files = [];
|
|
2020
|
+
const rules = [];
|
|
2021
|
+
const tokens = instruction.split(/\s+/);
|
|
2022
|
+
const remaining = [];
|
|
2023
|
+
for (const token of tokens) {
|
|
2024
|
+
if (token.startsWith("@") && token.length > 1) {
|
|
2025
|
+
files.push(token.slice(1));
|
|
2026
|
+
} else if (token.startsWith("#") && token.length > 1) {
|
|
2027
|
+
rules.push(token.slice(1));
|
|
2028
|
+
} else {
|
|
2029
|
+
remaining.push(token);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
return { cleaned: remaining.join(" "), files, rules };
|
|
2033
|
+
}
|
|
2034
|
+
async function sendToNexus(sshPort, instruction, files, rules, cwd) {
|
|
2035
|
+
const message = `[99] ${instruction}`;
|
|
2036
|
+
const payload = JSON.stringify({
|
|
2037
|
+
message,
|
|
2038
|
+
source: "99",
|
|
2039
|
+
context: { files, rules, cwd }
|
|
2040
|
+
});
|
|
2041
|
+
const escaped = shellEscape(payload);
|
|
2042
|
+
const { stdout, code } = await sshExec(
|
|
2043
|
+
sshPort,
|
|
2044
|
+
`curl -sf -X POST http://localhost:4200/message -H 'Content-Type: application/json' -d ${escaped}`
|
|
2045
|
+
);
|
|
2046
|
+
if (code !== 0) {
|
|
2047
|
+
throw new Error("NEXUS engine returned a non-zero exit code");
|
|
2048
|
+
}
|
|
2049
|
+
try {
|
|
2050
|
+
const parsed = JSON.parse(stdout);
|
|
2051
|
+
return parsed.response ?? parsed.message ?? stdout;
|
|
2052
|
+
} catch {
|
|
2053
|
+
return stdout;
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
var ninetyNineCommand = new Command12("99").description("AI pair-programming session backed by the full NEXUS agent engine").argument("[instruction...]", "What to build, edit, or debug").option("--edit <file>", "AI-assisted edit for a specific file").option("--search <query>", "Contextual codebase search").option("--debug", "Debug current issue with NEXUS agent assistance").action(async (instructionWords, opts) => {
|
|
2057
|
+
try {
|
|
2058
|
+
const config = loadConfig();
|
|
2059
|
+
if (!config) {
|
|
2060
|
+
log.error("No NEXUS configuration found. Run: buildwithnexus init");
|
|
2061
|
+
process.exit(1);
|
|
2062
|
+
}
|
|
2063
|
+
if (!isVmRunning()) {
|
|
2064
|
+
log.error("VM is not running. Start it with: buildwithnexus start");
|
|
2065
|
+
process.exit(1);
|
|
2066
|
+
}
|
|
2067
|
+
const spinner = createSpinner("Connecting to NEXUS...");
|
|
2068
|
+
spinner.start();
|
|
2069
|
+
const { stdout: healthCheck, code: healthCode } = await sshExec(
|
|
2070
|
+
config.sshPort,
|
|
2071
|
+
"curl -sf http://localhost:4200/health"
|
|
2072
|
+
);
|
|
2073
|
+
if (healthCode !== 0 || !healthCheck.includes("ok")) {
|
|
2074
|
+
fail(spinner, "NEXUS server is not healthy");
|
|
2075
|
+
log.warn("Check status: buildwithnexus status");
|
|
2076
|
+
process.exit(1);
|
|
2077
|
+
}
|
|
2078
|
+
succeed(spinner, "Connected to NEXUS");
|
|
2079
|
+
console.log("");
|
|
2080
|
+
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"));
|
|
2081
|
+
console.log(chalk12.bold(" \u2551 ") + chalk12.bold.green("/99 Pair Programming") + chalk12.dim(" \u2014 powered by NEXUS") + chalk12.bold(" \u2551"));
|
|
2082
|
+
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"));
|
|
2083
|
+
console.log(chalk12.bold(" \u2551 ") + chalk12.dim("Describe what you want changed. NEXUS engineers".padEnd(55)) + chalk12.bold("\u2551"));
|
|
2084
|
+
console.log(chalk12.bold(" \u2551 ") + chalk12.dim("analyze and modify your code in real time.".padEnd(55)) + chalk12.bold("\u2551"));
|
|
2085
|
+
console.log(chalk12.bold(" \u2551 ") + chalk12.dim("Use @file to attach context, #rule to load rules.".padEnd(55)) + chalk12.bold("\u2551"));
|
|
2086
|
+
console.log(chalk12.bold(" \u2551 ") + chalk12.dim("Type 'exit' or 'quit' to end the session.".padEnd(55)) + chalk12.bold("\u2551"));
|
|
2087
|
+
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"));
|
|
2088
|
+
console.log("");
|
|
2089
|
+
const cwd = process.cwd();
|
|
2090
|
+
if (opts.edit) {
|
|
2091
|
+
const instruction = instructionWords.join(" ") || await input4({
|
|
2092
|
+
message: `What change should be made to ${opts.edit}?`
|
|
2093
|
+
});
|
|
2094
|
+
const { cleaned, files, rules } = parsePrefixes(instruction);
|
|
2095
|
+
const fullInstruction = `Edit file ${opts.edit}: ${cleaned}`;
|
|
2096
|
+
console.log(`${YOU_PREFIX2}: ${chalk12.white(fullInstruction)}`);
|
|
2097
|
+
console.log(DIVIDER2);
|
|
2098
|
+
const thinking = createSpinner("NEXUS engineers analyzing the file...");
|
|
2099
|
+
thinking.start();
|
|
2100
|
+
const response = await sendToNexus(config.sshPort, fullInstruction, [opts.edit, ...files], rules, cwd);
|
|
2101
|
+
thinking.stop();
|
|
2102
|
+
thinking.clear();
|
|
2103
|
+
console.log(`${AGENT_PREFIX}`);
|
|
2104
|
+
console.log(formatAgentActivity(redact(response)));
|
|
2105
|
+
console.log(DIVIDER2);
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
if (opts.search) {
|
|
2109
|
+
const fullInstruction = `Search the codebase for: ${opts.search}`;
|
|
2110
|
+
console.log(`${YOU_PREFIX2}: ${chalk12.white(fullInstruction)}`);
|
|
2111
|
+
console.log(DIVIDER2);
|
|
2112
|
+
const thinking = createSpinner("Searching with NEXUS context...");
|
|
2113
|
+
thinking.start();
|
|
2114
|
+
const response = await sendToNexus(config.sshPort, fullInstruction, [], [], cwd);
|
|
2115
|
+
thinking.stop();
|
|
2116
|
+
thinking.clear();
|
|
2117
|
+
console.log(`${AGENT_PREFIX}`);
|
|
2118
|
+
console.log(formatAgentActivity(redact(response)));
|
|
2119
|
+
console.log(DIVIDER2);
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
if (opts.debug) {
|
|
2123
|
+
const fullInstruction = "Debug the current issue \u2014 analyze recent errors, identify the root cause, and propose a fix";
|
|
2124
|
+
console.log(`${YOU_PREFIX2}: ${chalk12.white(fullInstruction)}`);
|
|
2125
|
+
console.log(DIVIDER2);
|
|
2126
|
+
const thinking = createSpinner("NEXUS debugger agent analyzing...");
|
|
2127
|
+
thinking.start();
|
|
2128
|
+
const response = await sendToNexus(config.sshPort, fullInstruction, [], [], cwd);
|
|
2129
|
+
thinking.stop();
|
|
2130
|
+
thinking.clear();
|
|
2131
|
+
console.log(`${AGENT_PREFIX}`);
|
|
2132
|
+
console.log(formatAgentActivity(redact(response)));
|
|
2133
|
+
console.log(DIVIDER2);
|
|
2134
|
+
return;
|
|
2135
|
+
}
|
|
2136
|
+
let initialInstruction = instructionWords.length > 0 ? instructionWords.join(" ") : "";
|
|
2137
|
+
if (!initialInstruction) {
|
|
2138
|
+
initialInstruction = await input4({
|
|
2139
|
+
message: chalk12.green("99 \u276F")
|
|
2140
|
+
});
|
|
2141
|
+
if (!initialInstruction.trim()) {
|
|
2142
|
+
log.warn("No instruction provided");
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
let turn = 0;
|
|
2147
|
+
let currentInstruction = initialInstruction;
|
|
2148
|
+
while (true) {
|
|
2149
|
+
turn++;
|
|
2150
|
+
const { cleaned, files, rules } = parsePrefixes(currentInstruction);
|
|
2151
|
+
const display = currentInstruction;
|
|
2152
|
+
const nexusInstruction = cleaned || currentInstruction;
|
|
2153
|
+
if (turn === 1) {
|
|
2154
|
+
console.log(`${YOU_PREFIX2}: ${chalk12.white(display)}`);
|
|
2155
|
+
}
|
|
2156
|
+
if (files.length > 0) {
|
|
2157
|
+
console.log(chalk12.dim(` Attaching: ${files.join(", ")}`));
|
|
2158
|
+
}
|
|
2159
|
+
if (rules.length > 0) {
|
|
2160
|
+
console.log(chalk12.dim(` Rules: ${rules.join(", ")}`));
|
|
2161
|
+
}
|
|
2162
|
+
console.log(DIVIDER2);
|
|
2163
|
+
const thinking = createSpinner(
|
|
2164
|
+
turn === 1 ? "NEXUS agents analyzing and implementing..." : "NEXUS agents processing..."
|
|
2165
|
+
);
|
|
2166
|
+
thinking.start();
|
|
2167
|
+
const response = await sendToNexus(config.sshPort, nexusInstruction, files, rules, cwd);
|
|
2168
|
+
thinking.stop();
|
|
2169
|
+
thinking.clear();
|
|
2170
|
+
console.log(`${AGENT_PREFIX}`);
|
|
2171
|
+
console.log(formatAgentActivity(redact(response)));
|
|
2172
|
+
console.log(DIVIDER2);
|
|
2173
|
+
const followUp = await input4({
|
|
2174
|
+
message: chalk12.green("99 \u276F")
|
|
2175
|
+
});
|
|
2176
|
+
const trimmed = followUp.trim().toLowerCase();
|
|
2177
|
+
if (!trimmed || trimmed === "exit" || trimmed === "quit" || trimmed === "q") {
|
|
2178
|
+
console.log("");
|
|
2179
|
+
log.success("/99 session ended");
|
|
2180
|
+
console.log(chalk12.dim(" Run again: buildwithnexus 99"));
|
|
2181
|
+
console.log("");
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
console.log(`${YOU_PREFIX2}: ${chalk12.white(followUp)}`);
|
|
2185
|
+
currentInstruction = followUp;
|
|
2186
|
+
}
|
|
2187
|
+
} catch (err) {
|
|
2188
|
+
if (err.code === "ERR_USE_AFTER_CLOSE") {
|
|
2189
|
+
console.log("");
|
|
2190
|
+
log.success("/99 session ended");
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
const safeErr = redactError(err);
|
|
2194
|
+
log.error(`/99 failed: ${safeErr.message}`);
|
|
2195
|
+
process.exit(1);
|
|
2196
|
+
}
|
|
2197
|
+
});
|
|
2198
|
+
|
|
2199
|
+
// src/commands/shell.ts
|
|
2200
|
+
import { Command as Command13 } from "commander";
|
|
2201
|
+
import chalk15 from "chalk";
|
|
1826
2202
|
init_secrets();
|
|
1827
2203
|
init_qemu();
|
|
1828
2204
|
init_ssh();
|
|
@@ -1833,7 +2209,7 @@ init_secrets();
|
|
|
1833
2209
|
import readline from "readline";
|
|
1834
2210
|
import fs10 from "fs";
|
|
1835
2211
|
import path11 from "path";
|
|
1836
|
-
import
|
|
2212
|
+
import chalk13 from "chalk";
|
|
1837
2213
|
var HISTORY_FILE = path11.join(NEXUS_HOME2, "shell_history");
|
|
1838
2214
|
var MAX_HISTORY = 1e3;
|
|
1839
2215
|
var Repl = class {
|
|
@@ -1868,7 +2244,7 @@ var Repl = class {
|
|
|
1868
2244
|
this.rl = readline.createInterface({
|
|
1869
2245
|
input: process.stdin,
|
|
1870
2246
|
output: process.stdout,
|
|
1871
|
-
prompt:
|
|
2247
|
+
prompt: chalk13.bold.cyan("nexus") + chalk13.dim(" \u276F "),
|
|
1872
2248
|
history: this.history,
|
|
1873
2249
|
historySize: MAX_HISTORY,
|
|
1874
2250
|
terminal: true
|
|
@@ -1898,25 +2274,25 @@ var Repl = class {
|
|
|
1898
2274
|
try {
|
|
1899
2275
|
await cmd.handler();
|
|
1900
2276
|
} catch (err) {
|
|
1901
|
-
console.log(
|
|
2277
|
+
console.log(chalk13.red(` \u2717 Command failed: ${err.message}`));
|
|
1902
2278
|
}
|
|
1903
2279
|
this.rl?.prompt();
|
|
1904
2280
|
return;
|
|
1905
2281
|
}
|
|
1906
|
-
console.log(
|
|
2282
|
+
console.log(chalk13.yellow(` Unknown command: /${cmdName}. Type /help for available commands.`));
|
|
1907
2283
|
this.rl?.prompt();
|
|
1908
2284
|
return;
|
|
1909
2285
|
}
|
|
1910
2286
|
try {
|
|
1911
2287
|
await this.onMessage(trimmed);
|
|
1912
2288
|
} catch (err) {
|
|
1913
|
-
console.log(
|
|
2289
|
+
console.log(chalk13.red(` \u2717 ${err.message}`));
|
|
1914
2290
|
}
|
|
1915
2291
|
this.rl?.prompt();
|
|
1916
2292
|
});
|
|
1917
2293
|
this.rl.on("close", () => {
|
|
1918
2294
|
this.saveHistory();
|
|
1919
|
-
console.log(
|
|
2295
|
+
console.log(chalk13.dim("\n Session ended."));
|
|
1920
2296
|
process.exit(0);
|
|
1921
2297
|
});
|
|
1922
2298
|
this.rl.on("SIGINT", () => {
|
|
@@ -1925,15 +2301,15 @@ var Repl = class {
|
|
|
1925
2301
|
}
|
|
1926
2302
|
showHelp() {
|
|
1927
2303
|
console.log("");
|
|
1928
|
-
console.log(
|
|
1929
|
-
console.log(
|
|
2304
|
+
console.log(chalk13.bold(" Available Commands:"));
|
|
2305
|
+
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"));
|
|
1930
2306
|
for (const [name, cmd] of this.commands) {
|
|
1931
|
-
console.log(` ${
|
|
2307
|
+
console.log(` ${chalk13.cyan("/" + name.padEnd(14))} ${chalk13.dim(cmd.description)}`);
|
|
1932
2308
|
}
|
|
1933
|
-
console.log(` ${
|
|
1934
|
-
console.log(` ${
|
|
1935
|
-
console.log(
|
|
1936
|
-
console.log(
|
|
2309
|
+
console.log(` ${chalk13.cyan("/help".padEnd(15))} ${chalk13.dim("Show this help message")}`);
|
|
2310
|
+
console.log(` ${chalk13.cyan("/exit".padEnd(15))} ${chalk13.dim("Exit the shell")}`);
|
|
2311
|
+
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"));
|
|
2312
|
+
console.log(chalk13.dim(" Type anything else to chat with NEXUS"));
|
|
1937
2313
|
console.log("");
|
|
1938
2314
|
}
|
|
1939
2315
|
write(text) {
|
|
@@ -1959,17 +2335,17 @@ var Repl = class {
|
|
|
1959
2335
|
init_ssh();
|
|
1960
2336
|
init_secrets();
|
|
1961
2337
|
init_dlp();
|
|
1962
|
-
import
|
|
2338
|
+
import chalk14 from "chalk";
|
|
1963
2339
|
var ROLE_COLORS = {
|
|
1964
|
-
"Chief of Staff":
|
|
1965
|
-
"VP Engineering":
|
|
1966
|
-
"VP Product":
|
|
1967
|
-
"Senior Engineer":
|
|
1968
|
-
"Engineer":
|
|
1969
|
-
"QA Lead":
|
|
1970
|
-
"Security Engineer":
|
|
1971
|
-
"DevOps Engineer":
|
|
1972
|
-
"default":
|
|
2340
|
+
"Chief of Staff": chalk14.bold.cyan,
|
|
2341
|
+
"VP Engineering": chalk14.bold.blue,
|
|
2342
|
+
"VP Product": chalk14.bold.magenta,
|
|
2343
|
+
"Senior Engineer": chalk14.bold.green,
|
|
2344
|
+
"Engineer": chalk14.green,
|
|
2345
|
+
"QA Lead": chalk14.bold.yellow,
|
|
2346
|
+
"Security Engineer": chalk14.bold.red,
|
|
2347
|
+
"DevOps Engineer": chalk14.bold.hex("#FF8C00"),
|
|
2348
|
+
"default": chalk14.bold.white
|
|
1973
2349
|
};
|
|
1974
2350
|
function getColor(role) {
|
|
1975
2351
|
if (!role) return ROLE_COLORS["default"];
|
|
@@ -1980,15 +2356,15 @@ function formatEvent(event) {
|
|
|
1980
2356
|
const prefix = event.role ? color(` [${event.role}]`) : "";
|
|
1981
2357
|
switch (event.type) {
|
|
1982
2358
|
case "agent_thinking":
|
|
1983
|
-
return `${prefix} ${
|
|
2359
|
+
return `${prefix} ${chalk14.dim("thinking...")}`;
|
|
1984
2360
|
case "agent_response":
|
|
1985
2361
|
return `${prefix} ${redact(event.content ?? "")}`;
|
|
1986
2362
|
case "task_delegated":
|
|
1987
|
-
return `${prefix} ${
|
|
2363
|
+
return `${prefix} ${chalk14.dim("\u2192")} delegated to ${chalk14.bold(event.target ?? "agent")}`;
|
|
1988
2364
|
case "agent_complete":
|
|
1989
|
-
return `${prefix} ${
|
|
2365
|
+
return `${prefix} ${chalk14.green("\u2713")} ${chalk14.dim("complete")}`;
|
|
1990
2366
|
case "error":
|
|
1991
|
-
return ` ${
|
|
2367
|
+
return ` ${chalk14.red("\u2717")} ${redact(event.content ?? "Unknown error")}`;
|
|
1992
2368
|
case "heartbeat":
|
|
1993
2369
|
return null;
|
|
1994
2370
|
default:
|
|
@@ -2071,19 +2447,19 @@ async function sendMessage2(sshPort, message) {
|
|
|
2071
2447
|
}
|
|
2072
2448
|
}
|
|
2073
2449
|
function showShellBanner(health) {
|
|
2074
|
-
const check =
|
|
2075
|
-
const cross =
|
|
2450
|
+
const check = chalk15.green("\u2713");
|
|
2451
|
+
const cross = chalk15.red("\u2717");
|
|
2076
2452
|
console.log("");
|
|
2077
|
-
console.log(
|
|
2078
|
-
console.log(
|
|
2079
|
-
console.log(
|
|
2080
|
-
console.log(
|
|
2453
|
+
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"));
|
|
2454
|
+
console.log(chalk15.bold(" \u2551 ") + chalk15.bold.cyan("NEXUS Interactive Shell") + chalk15.bold(" \u2551"));
|
|
2455
|
+
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"));
|
|
2456
|
+
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"));
|
|
2081
2457
|
if (health.tunnelUrl) {
|
|
2082
|
-
console.log(
|
|
2458
|
+
console.log(chalk15.bold(" \u2551 ") + chalk15.dim(`Tunnel: ${health.tunnelUrl}`.padEnd(55)) + chalk15.bold("\u2551"));
|
|
2083
2459
|
}
|
|
2084
|
-
console.log(
|
|
2085
|
-
console.log(
|
|
2086
|
-
console.log(
|
|
2460
|
+
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"));
|
|
2461
|
+
console.log(chalk15.bold(" \u2551 ") + chalk15.dim("Type naturally to chat \xB7 /help for commands".padEnd(55)) + chalk15.bold("\u2551"));
|
|
2462
|
+
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"));
|
|
2087
2463
|
console.log("");
|
|
2088
2464
|
}
|
|
2089
2465
|
async function getAgentList(sshPort) {
|
|
@@ -2093,13 +2469,13 @@ async function getAgentList(sshPort) {
|
|
|
2093
2469
|
const agents = JSON.parse(stdout);
|
|
2094
2470
|
if (!Array.isArray(agents)) return stdout;
|
|
2095
2471
|
const lines = [""];
|
|
2096
|
-
lines.push(
|
|
2097
|
-
lines.push(
|
|
2472
|
+
lines.push(chalk15.bold(" Registered Agents:"));
|
|
2473
|
+
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"));
|
|
2098
2474
|
for (const agent of agents) {
|
|
2099
2475
|
const name = agent.name ?? agent.id ?? "unknown";
|
|
2100
2476
|
const role = agent.role ?? "";
|
|
2101
|
-
const status = agent.status === "active" ?
|
|
2102
|
-
lines.push(` ${status} ${
|
|
2477
|
+
const status = agent.status === "active" ? chalk15.green("\u25CF") : chalk15.dim("\u25CB");
|
|
2478
|
+
lines.push(` ${status} ${chalk15.bold(name.padEnd(24))} ${chalk15.dim(role)}`);
|
|
2103
2479
|
}
|
|
2104
2480
|
lines.push("");
|
|
2105
2481
|
return lines.join("\n");
|
|
@@ -2111,11 +2487,11 @@ async function getStatus(sshPort) {
|
|
|
2111
2487
|
try {
|
|
2112
2488
|
const vmRunning = isVmRunning();
|
|
2113
2489
|
const health = await checkHealth(sshPort, vmRunning);
|
|
2114
|
-
const check =
|
|
2115
|
-
const cross =
|
|
2490
|
+
const check = chalk15.green("\u2713");
|
|
2491
|
+
const cross = chalk15.red("\u2717");
|
|
2116
2492
|
const lines = [""];
|
|
2117
|
-
lines.push(
|
|
2118
|
-
lines.push(
|
|
2493
|
+
lines.push(chalk15.bold(" System Status:"));
|
|
2494
|
+
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"));
|
|
2119
2495
|
lines.push(` ${health.vmRunning ? check : cross} Virtual Machine`);
|
|
2120
2496
|
lines.push(` ${health.sshReady ? check : cross} SSH Connection`);
|
|
2121
2497
|
lines.push(` ${health.dockerReady ? check : cross} Docker Engine`);
|
|
@@ -2137,17 +2513,17 @@ async function getCost(sshPort) {
|
|
|
2137
2513
|
if (code !== 0) return " Could not retrieve cost data";
|
|
2138
2514
|
const data = JSON.parse(stdout);
|
|
2139
2515
|
const lines = [""];
|
|
2140
|
-
lines.push(
|
|
2141
|
-
lines.push(
|
|
2516
|
+
lines.push(chalk15.bold(" Token Costs:"));
|
|
2517
|
+
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"));
|
|
2142
2518
|
if (data.total !== void 0) {
|
|
2143
|
-
lines.push(` Total: ${
|
|
2519
|
+
lines.push(` Total: ${chalk15.bold.green("$" + Number(data.total).toFixed(4))}`);
|
|
2144
2520
|
}
|
|
2145
2521
|
if (data.today !== void 0) {
|
|
2146
|
-
lines.push(` Today: ${
|
|
2522
|
+
lines.push(` Today: ${chalk15.bold("$" + Number(data.today).toFixed(4))}`);
|
|
2147
2523
|
}
|
|
2148
2524
|
if (data.by_agent && typeof data.by_agent === "object") {
|
|
2149
2525
|
lines.push("");
|
|
2150
|
-
lines.push(
|
|
2526
|
+
lines.push(chalk15.dim(" By Agent:"));
|
|
2151
2527
|
for (const [agent, cost] of Object.entries(data.by_agent)) {
|
|
2152
2528
|
lines.push(` ${agent.padEnd(20)} $${Number(cost).toFixed(4)}`);
|
|
2153
2529
|
}
|
|
@@ -2158,7 +2534,7 @@ async function getCost(sshPort) {
|
|
|
2158
2534
|
return " Could not retrieve cost data";
|
|
2159
2535
|
}
|
|
2160
2536
|
}
|
|
2161
|
-
var shellCommand2 = new
|
|
2537
|
+
var shellCommand2 = new Command13("shell").description("Launch the interactive NEXUS shell").action(async () => {
|
|
2162
2538
|
try {
|
|
2163
2539
|
const config = loadConfig();
|
|
2164
2540
|
if (!config) {
|
|
@@ -2192,10 +2568,10 @@ var shellCommand2 = new Command12("shell").description("Launch the interactive N
|
|
|
2192
2568
|
thinkingSpinner.stop();
|
|
2193
2569
|
thinkingSpinner.clear();
|
|
2194
2570
|
console.log("");
|
|
2195
|
-
console.log(
|
|
2571
|
+
console.log(chalk15.bold.cyan(" Chief of Staff:"));
|
|
2196
2572
|
const lines = redact(response).split("\n");
|
|
2197
2573
|
for (const line of lines) {
|
|
2198
|
-
console.log(
|
|
2574
|
+
console.log(chalk15.white(" " + line));
|
|
2199
2575
|
}
|
|
2200
2576
|
console.log("");
|
|
2201
2577
|
} catch (err) {
|
|
@@ -2209,17 +2585,17 @@ var shellCommand2 = new Command12("shell").description("Launch the interactive N
|
|
|
2209
2585
|
description: "Brainstorm with the full NEXUS org (led by Chief of Staff)",
|
|
2210
2586
|
handler: async () => {
|
|
2211
2587
|
console.log("");
|
|
2212
|
-
console.log(
|
|
2213
|
-
console.log(
|
|
2214
|
-
console.log(
|
|
2215
|
-
console.log(
|
|
2216
|
-
console.log(
|
|
2217
|
-
console.log(
|
|
2218
|
-
console.log(
|
|
2219
|
-
console.log(
|
|
2588
|
+
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"));
|
|
2589
|
+
console.log(chalk15.bold(" \u2551 ") + chalk15.bold.cyan("NEXUS Brainstorm Session") + chalk15.bold(" \u2551"));
|
|
2590
|
+
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"));
|
|
2591
|
+
console.log(chalk15.bold(" \u2551 ") + chalk15.dim("The Chief of Staff will facilitate a discussion with".padEnd(55)) + chalk15.bold("\u2551"));
|
|
2592
|
+
console.log(chalk15.bold(" \u2551 ") + chalk15.dim("the full NEXUS org to refine your idea. When ready,".padEnd(55)) + chalk15.bold("\u2551"));
|
|
2593
|
+
console.log(chalk15.bold(" \u2551 ") + chalk15.dim("NEXUS will draft an execution plan for your review.".padEnd(55)) + chalk15.bold("\u2551"));
|
|
2594
|
+
console.log(chalk15.bold(" \u2551 ") + chalk15.dim("Type 'exit' to end brainstorm. Type 'plan' to hand off.".padEnd(55)) + chalk15.bold("\u2551"));
|
|
2595
|
+
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"));
|
|
2220
2596
|
console.log("");
|
|
2221
|
-
const { input:
|
|
2222
|
-
const idea = await
|
|
2597
|
+
const { input: input5 } = await import("@inquirer/prompts");
|
|
2598
|
+
const idea = await input5({ message: "What would you like to brainstorm?" });
|
|
2223
2599
|
if (!idea.trim()) return;
|
|
2224
2600
|
let currentMessage = `[BRAINSTORM] The CEO wants to brainstorm the following idea. As Chief of Staff, facilitate a discussion with the entire NEXUS organization \u2014 involve VPs, engineers, QA, security, and any relevant specialists. Gather diverse perspectives, identify risks and opportunities, and help refine the idea. Do NOT execute \u2014 only discuss, analyze, and recommend. Idea: ${idea}`;
|
|
2225
2601
|
while (true) {
|
|
@@ -2229,12 +2605,12 @@ var shellCommand2 = new Command12("shell").description("Launch the interactive N
|
|
|
2229
2605
|
brainstormSpinner.stop();
|
|
2230
2606
|
brainstormSpinner.clear();
|
|
2231
2607
|
console.log("");
|
|
2232
|
-
console.log(
|
|
2608
|
+
console.log(chalk15.bold.cyan(" Chief of Staff:"));
|
|
2233
2609
|
for (const line of redact(response).split("\n")) {
|
|
2234
|
-
console.log(
|
|
2610
|
+
console.log(chalk15.white(" " + line));
|
|
2235
2611
|
}
|
|
2236
2612
|
console.log("");
|
|
2237
|
-
const followUp = await
|
|
2613
|
+
const followUp = await input5({ message: chalk15.bold("You:") });
|
|
2238
2614
|
const trimmed = followUp.trim().toLowerCase();
|
|
2239
2615
|
if (!trimmed || trimmed === "exit" || trimmed === "quit") {
|
|
2240
2616
|
console.log("");
|
|
@@ -2248,9 +2624,9 @@ var shellCommand2 = new Command12("shell").description("Launch the interactive N
|
|
|
2248
2624
|
planSpinner.stop();
|
|
2249
2625
|
planSpinner.clear();
|
|
2250
2626
|
console.log("");
|
|
2251
|
-
console.log(
|
|
2627
|
+
console.log(chalk15.bold.green(" Execution Plan:"));
|
|
2252
2628
|
for (const line of redact(planResponse).split("\n")) {
|
|
2253
|
-
console.log(
|
|
2629
|
+
console.log(chalk15.white(" " + line));
|
|
2254
2630
|
}
|
|
2255
2631
|
console.log("");
|
|
2256
2632
|
log.success("Plan drafted. Use the shell to refine or approve.");
|
|
@@ -2290,17 +2666,17 @@ var shellCommand2 = new Command12("shell").description("Launch the interactive N
|
|
|
2290
2666
|
handler: async () => {
|
|
2291
2667
|
const { stdout } = await sshExec(config.sshPort, "tail -30 /home/nexus/.nexus/logs/server.log 2>/dev/null");
|
|
2292
2668
|
console.log("");
|
|
2293
|
-
console.log(
|
|
2294
|
-
console.log(
|
|
2669
|
+
console.log(chalk15.bold(" Recent Logs:"));
|
|
2670
|
+
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"));
|
|
2295
2671
|
for (const line of redact(stdout).split("\n")) {
|
|
2296
|
-
console.log(
|
|
2672
|
+
console.log(chalk15.dim(" " + line));
|
|
2297
2673
|
}
|
|
2298
2674
|
console.log("");
|
|
2299
2675
|
}
|
|
2300
2676
|
});
|
|
2301
2677
|
repl.registerCommand({
|
|
2302
2678
|
name: "ssh",
|
|
2303
|
-
description: "
|
|
2679
|
+
description: "Drop into the VM for debugging/inspection",
|
|
2304
2680
|
handler: async () => {
|
|
2305
2681
|
const { openInteractiveSsh: openInteractiveSsh2 } = await Promise.resolve().then(() => (init_ssh(), ssh_exports));
|
|
2306
2682
|
eventStream.stop();
|
|
@@ -2308,6 +2684,136 @@ var shellCommand2 = new Command12("shell").description("Launch the interactive N
|
|
|
2308
2684
|
eventStream.start();
|
|
2309
2685
|
}
|
|
2310
2686
|
});
|
|
2687
|
+
repl.registerCommand({
|
|
2688
|
+
name: "org-chart",
|
|
2689
|
+
description: "Display the NEXUS organizational hierarchy",
|
|
2690
|
+
handler: async () => {
|
|
2691
|
+
console.log("");
|
|
2692
|
+
console.log(chalk15.bold(" NEXUS Organizational Hierarchy"));
|
|
2693
|
+
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"));
|
|
2694
|
+
console.log(` ${chalk15.bold.white("You")} ${chalk15.dim("(CEO)")}`);
|
|
2695
|
+
console.log(` \u2514\u2500\u2500 ${chalk15.bold.cyan("Chief of Staff")} ${chalk15.dim("\u2014 orchestrates all work, your direct interface")}`);
|
|
2696
|
+
console.log(` \u251C\u2500\u2500 ${chalk15.bold.blue("VP Engineering")} ${chalk15.dim("\u2014 owns technical execution")}`);
|
|
2697
|
+
console.log(` \u2502 \u251C\u2500\u2500 ${chalk15.green("Senior Engineer")} ${chalk15.dim("\xD7 8 \u2014 implementation, refactoring")}`);
|
|
2698
|
+
console.log(` \u2502 \u251C\u2500\u2500 ${chalk15.green("Engineer")} ${chalk15.dim("\xD7 12 \u2014 feature work, bug fixes")}`);
|
|
2699
|
+
console.log(` \u2502 \u2514\u2500\u2500 ${chalk15.hex("#FF8C00")("DevOps Engineer")} ${chalk15.dim("\xD7 4 \u2014 CI/CD, Docker, infra")}`);
|
|
2700
|
+
console.log(` \u251C\u2500\u2500 ${chalk15.bold.magenta("VP Product")} ${chalk15.dim("\u2014 owns roadmap and priorities")}`);
|
|
2701
|
+
console.log(` \u2502 \u251C\u2500\u2500 ${chalk15.magenta("Product Manager")} ${chalk15.dim("\xD7 3 \u2014 specs, requirements")}`);
|
|
2702
|
+
console.log(` \u2502 \u2514\u2500\u2500 ${chalk15.magenta("Designer")} ${chalk15.dim("\xD7 2 \u2014 UI/UX, prototyping")}`);
|
|
2703
|
+
console.log(` \u251C\u2500\u2500 ${chalk15.bold.yellow("QA Lead")} ${chalk15.dim("\u2014 owns quality assurance")}`);
|
|
2704
|
+
console.log(` \u2502 \u2514\u2500\u2500 ${chalk15.yellow("QA Engineer")} ${chalk15.dim("\xD7 6 \u2014 testing, coverage, validation")}`);
|
|
2705
|
+
console.log(` \u251C\u2500\u2500 ${chalk15.bold.red("Security Engineer")} ${chalk15.dim("\xD7 4 \u2014 auth, scanning, compliance")}`);
|
|
2706
|
+
console.log(` \u2514\u2500\u2500 ${chalk15.bold.white("Knowledge Manager")} ${chalk15.dim("\u2014 RAG, documentation, learning")}`);
|
|
2707
|
+
console.log("");
|
|
2708
|
+
console.log(chalk15.dim(" 56 agents total \xB7 Self-learning ML pipeline"));
|
|
2709
|
+
console.log(chalk15.dim(" Full details: https://buildwithnexus.dev/overview"));
|
|
2710
|
+
console.log("");
|
|
2711
|
+
}
|
|
2712
|
+
});
|
|
2713
|
+
repl.registerCommand({
|
|
2714
|
+
name: "security",
|
|
2715
|
+
description: "Show the security posture of this runtime",
|
|
2716
|
+
handler: async () => {
|
|
2717
|
+
const { showSecurityPosture: showSecurityPosture2 } = await Promise.resolve().then(() => (init_banner(), banner_exports));
|
|
2718
|
+
showSecurityPosture2();
|
|
2719
|
+
}
|
|
2720
|
+
});
|
|
2721
|
+
repl.registerCommand({
|
|
2722
|
+
name: "tutorial",
|
|
2723
|
+
description: "Guided walkthrough of NEXUS capabilities",
|
|
2724
|
+
handler: async () => {
|
|
2725
|
+
const { input: input5 } = await import("@inquirer/prompts");
|
|
2726
|
+
const steps = [
|
|
2727
|
+
{
|
|
2728
|
+
title: "Welcome to NEXUS",
|
|
2729
|
+
content: [
|
|
2730
|
+
"NEXUS is a 56-agent autonomous engineering organization.",
|
|
2731
|
+
"You are the CEO. The Chief of Staff leads your team.",
|
|
2732
|
+
"",
|
|
2733
|
+
"When you type a request, the Chief of Staff:",
|
|
2734
|
+
" 1. Analyzes what needs to be done",
|
|
2735
|
+
" 2. Delegates to the right VPs and specialists",
|
|
2736
|
+
" 3. Agents collaborate, review each other's work",
|
|
2737
|
+
" 4. Results stream back to you in real time"
|
|
2738
|
+
]
|
|
2739
|
+
},
|
|
2740
|
+
{
|
|
2741
|
+
title: "The Org Chart",
|
|
2742
|
+
content: [
|
|
2743
|
+
"You \u2192 Chief of Staff \u2192 VPs \u2192 Engineers",
|
|
2744
|
+
"",
|
|
2745
|
+
" VP Engineering \u2014 24 engineers, DevOps",
|
|
2746
|
+
" VP Product \u2014 PMs, designers",
|
|
2747
|
+
" QA Lead \u2014 6 QA engineers",
|
|
2748
|
+
" Security \u2014 4 security engineers",
|
|
2749
|
+
" Knowledge \u2014 RAG, documentation",
|
|
2750
|
+
"",
|
|
2751
|
+
"Run /org-chart to see the full hierarchy."
|
|
2752
|
+
]
|
|
2753
|
+
},
|
|
2754
|
+
{
|
|
2755
|
+
title: "Try It: Natural Language",
|
|
2756
|
+
content: [
|
|
2757
|
+
"Just type what you want built. Examples:",
|
|
2758
|
+
"",
|
|
2759
|
+
' "Build a REST API with JWT authentication"',
|
|
2760
|
+
' "Fix the memory leak in the worker pool"',
|
|
2761
|
+
' "Refactor the database layer to use connection pooling"',
|
|
2762
|
+
"",
|
|
2763
|
+
"NEXUS assigns the right agents automatically.",
|
|
2764
|
+
"You'll see their thinking and delegation in real time."
|
|
2765
|
+
]
|
|
2766
|
+
},
|
|
2767
|
+
{
|
|
2768
|
+
title: "Brainstorming",
|
|
2769
|
+
content: [
|
|
2770
|
+
"Use /brainstorm to explore ideas before committing.",
|
|
2771
|
+
"",
|
|
2772
|
+
"The Chief of Staff facilitates an org-wide discussion:",
|
|
2773
|
+
" - Engineers assess technical feasibility",
|
|
2774
|
+
" - Security flags risks",
|
|
2775
|
+
" - Product suggests user impact",
|
|
2776
|
+
" - QA identifies testing needs",
|
|
2777
|
+
"",
|
|
2778
|
+
"Type 'plan' when ready to hand off for execution."
|
|
2779
|
+
]
|
|
2780
|
+
},
|
|
2781
|
+
{
|
|
2782
|
+
title: "Monitoring & Control",
|
|
2783
|
+
content: [
|
|
2784
|
+
" /status \u2014 System health (VM, SSH, Docker, Engine)",
|
|
2785
|
+
" /agents \u2014 List all 56 registered agents",
|
|
2786
|
+
" /cost \u2014 Token usage and spend tracking",
|
|
2787
|
+
" /logs \u2014 Server logs for debugging",
|
|
2788
|
+
" /ssh \u2014 Drop into the VM directly",
|
|
2789
|
+
" /security \u2014 View the security posture",
|
|
2790
|
+
"",
|
|
2791
|
+
"You're in control. NEXUS executes."
|
|
2792
|
+
]
|
|
2793
|
+
}
|
|
2794
|
+
];
|
|
2795
|
+
for (let i = 0; i < steps.length; i++) {
|
|
2796
|
+
const step = steps[i];
|
|
2797
|
+
console.log("");
|
|
2798
|
+
console.log(chalk15.bold(` \u2500\u2500 ${chalk15.cyan(`Step ${i + 1}/${steps.length}`)} \u2500\u2500 ${step.title} \u2500\u2500`));
|
|
2799
|
+
console.log("");
|
|
2800
|
+
for (const line of step.content) {
|
|
2801
|
+
console.log(chalk15.white(" " + line));
|
|
2802
|
+
}
|
|
2803
|
+
console.log("");
|
|
2804
|
+
if (i < steps.length - 1) {
|
|
2805
|
+
const next = await input5({ message: chalk15.dim("Press Enter to continue (or 'skip' to exit)") });
|
|
2806
|
+
if (next.trim().toLowerCase() === "skip") {
|
|
2807
|
+
log.success("Tutorial ended. Type /help to see all commands.");
|
|
2808
|
+
return;
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
console.log("");
|
|
2813
|
+
log.success("Tutorial complete! Start typing to talk to NEXUS.");
|
|
2814
|
+
console.log("");
|
|
2815
|
+
}
|
|
2816
|
+
});
|
|
2311
2817
|
process.on("SIGINT", () => {
|
|
2312
2818
|
eventStream.stop();
|
|
2313
2819
|
repl.stop();
|
|
@@ -2326,7 +2832,7 @@ var shellCommand2 = new Command12("shell").description("Launch the interactive N
|
|
|
2326
2832
|
});
|
|
2327
2833
|
|
|
2328
2834
|
// src/cli.ts
|
|
2329
|
-
var cli = new
|
|
2835
|
+
var cli = new Command14().name("buildwithnexus").description("Auto-scaffold and launch a fully autonomous NEXUS runtime").version("0.3.1");
|
|
2330
2836
|
cli.addCommand(initCommand);
|
|
2331
2837
|
cli.addCommand(startCommand);
|
|
2332
2838
|
cli.addCommand(stopCommand);
|
|
@@ -2338,13 +2844,14 @@ cli.addCommand(destroyCommand);
|
|
|
2338
2844
|
cli.addCommand(keysCommand);
|
|
2339
2845
|
cli.addCommand(sshCommand);
|
|
2340
2846
|
cli.addCommand(brainstormCommand);
|
|
2847
|
+
cli.addCommand(ninetyNineCommand);
|
|
2341
2848
|
cli.addCommand(shellCommand2);
|
|
2342
2849
|
cli.action(async () => {
|
|
2343
2850
|
try {
|
|
2344
2851
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_secrets(), secrets_exports));
|
|
2345
|
-
const { isVmRunning:
|
|
2852
|
+
const { isVmRunning: isVmRunning2 } = await Promise.resolve().then(() => (init_qemu(), qemu_exports));
|
|
2346
2853
|
const config = loadConfig2();
|
|
2347
|
-
if (config &&
|
|
2854
|
+
if (config && isVmRunning2()) {
|
|
2348
2855
|
await shellCommand2.parseAsync([], { from: "user" });
|
|
2349
2856
|
return;
|
|
2350
2857
|
}
|
|
@@ -2358,7 +2865,7 @@ import fs11 from "fs";
|
|
|
2358
2865
|
import path12 from "path";
|
|
2359
2866
|
import os3 from "os";
|
|
2360
2867
|
import https from "https";
|
|
2361
|
-
import
|
|
2868
|
+
import chalk16 from "chalk";
|
|
2362
2869
|
var PACKAGE_NAME = "buildwithnexus";
|
|
2363
2870
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
2364
2871
|
var STATE_DIR = path12.join(os3.homedir(), ".buildwithnexus");
|
|
@@ -2439,8 +2946,8 @@ async function checkForUpdates(currentVersion) {
|
|
|
2439
2946
|
function printUpdateBanner(current, latest) {
|
|
2440
2947
|
const msg = [
|
|
2441
2948
|
"",
|
|
2442
|
-
|
|
2443
|
-
|
|
2949
|
+
chalk16.yellow(` Update available: ${current} \u2192 ${latest}`),
|
|
2950
|
+
chalk16.cyan(` Run: npm update -g buildwithnexus`),
|
|
2444
2951
|
""
|
|
2445
2952
|
].join("\n");
|
|
2446
2953
|
process.stderr.write(msg + "\n");
|