buildwithnexus 0.2.8 → 0.2.9
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 +892 -290
- package/dist/nexus-release.tar.gz +0 -0
- package/dist/templates/cloud-init.yaml.ejs +22 -21
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1,116 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
var BANNER = `
|
|
11
|
-
\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
|
|
12
|
-
\u2551 ${chalk.bold.cyan("B U I L D W I T H N E X U S")} \u2551
|
|
13
|
-
\u2551 \u2551
|
|
14
|
-
\u2551 Autonomous AI Runtime \xB7 Nested Isolation \u2551
|
|
15
|
-
\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
|
|
16
|
-
`;
|
|
17
|
-
function showBanner() {
|
|
18
|
-
console.log(BANNER);
|
|
19
|
-
console.log(chalk.dim(" v0.2.8 \xB7 buildwithnexus.dev\n"));
|
|
20
|
-
}
|
|
21
|
-
function showPhase(phase, total, description) {
|
|
22
|
-
const progress = chalk.cyan(`[${phase}/${total}]`);
|
|
23
|
-
console.log(`
|
|
24
|
-
${progress} ${chalk.bold(description)}`);
|
|
25
|
-
}
|
|
26
|
-
function showCompletion(urls) {
|
|
27
|
-
const lines = [
|
|
28
|
-
"",
|
|
29
|
-
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"),
|
|
30
|
-
chalk.green(" \u2551 ") + chalk.bold.green("NEXUS Runtime is Live!") + chalk.green(" \u2551"),
|
|
31
|
-
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"),
|
|
32
|
-
chalk.green(" \u2551 ") + chalk.white(`Connect: ${urls.ssh}`.padEnd(55)) + chalk.green("\u2551")
|
|
33
|
-
];
|
|
34
|
-
if (urls.remote) {
|
|
35
|
-
lines.push(chalk.green(" \u2551 ") + chalk.white(`Remote: ${urls.remote}`.padEnd(55)) + chalk.green("\u2551"));
|
|
36
|
-
}
|
|
37
|
-
lines.push(
|
|
38
|
-
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"),
|
|
39
|
-
chalk.green(" \u2551 ") + chalk.dim("Commands:".padEnd(55)) + chalk.green("\u2551"),
|
|
40
|
-
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus ssh - Open CLI".padEnd(55)) + chalk.green("\u2551"),
|
|
41
|
-
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus status - Check health".padEnd(55)) + chalk.green("\u2551"),
|
|
42
|
-
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus logs - View logs".padEnd(55)) + chalk.green("\u2551"),
|
|
43
|
-
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus stop - Shutdown".padEnd(55)) + chalk.green("\u2551"),
|
|
44
|
-
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus start - Restart".padEnd(55)) + chalk.green("\u2551"),
|
|
45
|
-
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus update - Update release".padEnd(55)) + chalk.green("\u2551"),
|
|
46
|
-
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus brainstorm - Brainstorm ideas".padEnd(55)) + chalk.green("\u2551"),
|
|
47
|
-
chalk.green(" \u2551 ") + chalk.white(" buildwithnexus destroy - Remove all".padEnd(55)) + chalk.green("\u2551"),
|
|
48
|
-
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"),
|
|
49
|
-
""
|
|
50
|
-
);
|
|
51
|
-
console.log(lines.join("\n"));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// src/ui/spinner.ts
|
|
55
|
-
import ora from "ora";
|
|
56
|
-
import chalk2 from "chalk";
|
|
57
|
-
function createSpinner(text) {
|
|
58
|
-
return ora({ text, color: "cyan", spinner: "dots" });
|
|
59
|
-
}
|
|
60
|
-
function succeed(spinner, text) {
|
|
61
|
-
spinner.succeed(chalk2.green(text));
|
|
62
|
-
}
|
|
63
|
-
function fail(spinner, text) {
|
|
64
|
-
spinner.fail(chalk2.red(text));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// src/ui/logger.ts
|
|
68
|
-
import chalk3 from "chalk";
|
|
69
|
-
var log = {
|
|
70
|
-
step(msg) {
|
|
71
|
-
console.log(chalk3.cyan(" \u2192 ") + msg);
|
|
72
|
-
},
|
|
73
|
-
success(msg) {
|
|
74
|
-
console.log(chalk3.green(" \u2713 ") + msg);
|
|
75
|
-
},
|
|
76
|
-
error(msg) {
|
|
77
|
-
console.error(chalk3.red(" \u2717 ") + msg);
|
|
78
|
-
},
|
|
79
|
-
warn(msg) {
|
|
80
|
-
console.log(chalk3.yellow(" \u26A0 ") + msg);
|
|
81
|
-
},
|
|
82
|
-
dim(msg) {
|
|
83
|
-
console.log(chalk3.dim(" " + msg));
|
|
84
|
-
},
|
|
85
|
-
detail(label, value) {
|
|
86
|
-
console.log(chalk3.dim(" " + label + ": ") + value);
|
|
87
|
-
}
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
88
10
|
};
|
|
89
|
-
|
|
90
|
-
// src/ui/prompts.ts
|
|
91
|
-
import { input, confirm, password } from "@inquirer/prompts";
|
|
92
|
-
import chalk4 from "chalk";
|
|
93
11
|
|
|
94
12
|
// src/core/dlp.ts
|
|
95
13
|
import crypto from "crypto";
|
|
96
14
|
import fs from "fs";
|
|
97
15
|
import path from "path";
|
|
98
|
-
var NEXUS_HOME = path.join(process.env.HOME || "~", ".buildwithnexus");
|
|
99
|
-
var SECRET_PATTERNS = [
|
|
100
|
-
/sk-ant-api03-[A-Za-z0-9_-]{20,}/g,
|
|
101
|
-
// Anthropic API key
|
|
102
|
-
/sk-[A-Za-z0-9]{20,}/g,
|
|
103
|
-
// OpenAI API key
|
|
104
|
-
/AIza[A-Za-z0-9_-]{35}/g
|
|
105
|
-
// Google AI API key
|
|
106
|
-
];
|
|
107
|
-
var FORBIDDEN_KEY_CHARS = /[\n\r\t'"\\`${}();&|<>!#%^]/;
|
|
108
|
-
var KEY_VALIDATORS = {
|
|
109
|
-
ANTHROPIC_API_KEY: /^sk-ant-[A-Za-z0-9_-]{20,}$/,
|
|
110
|
-
OPENAI_API_KEY: /^sk-[A-Za-z0-9_-]{20,}$/,
|
|
111
|
-
GOOGLE_API_KEY: /^AIza[A-Za-z0-9_-]{35,}$/,
|
|
112
|
-
NEXUS_MASTER_SECRET: /^[A-Za-z0-9_-]{20,64}$/
|
|
113
|
-
};
|
|
114
16
|
function yamlEscape(value) {
|
|
115
17
|
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t").replace(/\0/g, "");
|
|
116
18
|
}
|
|
@@ -144,12 +46,6 @@ function redactError(err) {
|
|
|
144
46
|
}
|
|
145
47
|
return new Error(redact(String(err)));
|
|
146
48
|
}
|
|
147
|
-
var DlpViolation = class extends Error {
|
|
148
|
-
constructor(message) {
|
|
149
|
-
super(message);
|
|
150
|
-
this.name = "DlpViolation";
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
49
|
function validateKeyValue(keyName, value) {
|
|
154
50
|
if (FORBIDDEN_KEY_CHARS.test(value)) {
|
|
155
51
|
throw new DlpViolation(
|
|
@@ -180,7 +76,6 @@ function validateAllKeys(keys) {
|
|
|
180
76
|
}
|
|
181
77
|
return violations;
|
|
182
78
|
}
|
|
183
|
-
var HMAC_PATH = path.join(NEXUS_HOME, ".keys.hmac");
|
|
184
79
|
function computeFileHmac(filePath, secret) {
|
|
185
80
|
const content = fs.readFileSync(filePath);
|
|
186
81
|
return crypto.createHmac("sha256", secret).update(content).digest("hex");
|
|
@@ -205,8 +100,6 @@ function verifyKeysFile(keysPath, masterSecret) {
|
|
|
205
100
|
return false;
|
|
206
101
|
}
|
|
207
102
|
}
|
|
208
|
-
var AUDIT_PATH = path.join(NEXUS_HOME, "audit.log");
|
|
209
|
-
var MAX_AUDIT_SIZE = 10 * 1024 * 1024;
|
|
210
103
|
function audit(event, detail = "") {
|
|
211
104
|
try {
|
|
212
105
|
const dir = path.dirname(AUDIT_PATH);
|
|
@@ -233,20 +126,6 @@ function audit(event, detail = "") {
|
|
|
233
126
|
} catch {
|
|
234
127
|
}
|
|
235
128
|
}
|
|
236
|
-
var SCRUB_KEYS = [
|
|
237
|
-
"ANTHROPIC_API_KEY",
|
|
238
|
-
"OPENAI_API_KEY",
|
|
239
|
-
"GOOGLE_API_KEY",
|
|
240
|
-
"NEXUS_MASTER_SECRET",
|
|
241
|
-
"NEXUS_SECRET",
|
|
242
|
-
"AWS_SECRET_ACCESS_KEY",
|
|
243
|
-
"AWS_SESSION_TOKEN",
|
|
244
|
-
"GITHUB_TOKEN",
|
|
245
|
-
"GH_TOKEN",
|
|
246
|
-
"NPM_TOKEN",
|
|
247
|
-
"DOCKER_PASSWORD",
|
|
248
|
-
"CI_JOB_TOKEN"
|
|
249
|
-
];
|
|
250
129
|
function scrubEnv() {
|
|
251
130
|
const clean = { ...process.env };
|
|
252
131
|
for (const key of SCRUB_KEYS) {
|
|
@@ -265,145 +144,69 @@ function scrubEnv() {
|
|
|
265
144
|
}
|
|
266
145
|
return clean;
|
|
267
146
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
message: "OpenAI API key (optional, press Enter to skip):",
|
|
288
|
-
mask: "*",
|
|
289
|
-
validate: (val) => {
|
|
290
|
-
if (!val) return true;
|
|
291
|
-
try {
|
|
292
|
-
validateKeyValue("OPENAI_API_KEY", val);
|
|
293
|
-
} catch (err) {
|
|
294
|
-
if (err instanceof DlpViolation) return err.message;
|
|
295
|
-
}
|
|
296
|
-
return true;
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
const googleKey = await password({
|
|
300
|
-
message: "Google AI API key (optional, press Enter to skip):",
|
|
301
|
-
mask: "*",
|
|
302
|
-
validate: (val) => {
|
|
303
|
-
if (!val) return true;
|
|
304
|
-
try {
|
|
305
|
-
validateKeyValue("GOOGLE_API_KEY", val);
|
|
306
|
-
} catch (err) {
|
|
307
|
-
if (err instanceof DlpViolation) return err.message;
|
|
308
|
-
}
|
|
309
|
-
return true;
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
console.log(chalk4.bold("\n VM Resources\n"));
|
|
313
|
-
const vmRam = Number(
|
|
314
|
-
await input({
|
|
315
|
-
message: "VM RAM in GB:",
|
|
316
|
-
default: "4",
|
|
317
|
-
validate: (v) => {
|
|
318
|
-
const n = Number(v);
|
|
319
|
-
if (!Number.isInteger(n) || n < 2 || n > 256) return "Must be a whole number between 2 and 256";
|
|
320
|
-
return true;
|
|
321
|
-
}
|
|
322
|
-
})
|
|
323
|
-
);
|
|
324
|
-
const vmCpus = Number(
|
|
325
|
-
await input({
|
|
326
|
-
message: "VM CPUs:",
|
|
327
|
-
default: "2",
|
|
328
|
-
validate: (v) => {
|
|
329
|
-
const n = Number(v);
|
|
330
|
-
if (!Number.isInteger(n) || n < 1 || n > 64) return "Must be a whole number between 1 and 64";
|
|
331
|
-
return true;
|
|
332
|
-
}
|
|
333
|
-
})
|
|
334
|
-
);
|
|
335
|
-
const vmDisk = Number(
|
|
336
|
-
await input({
|
|
337
|
-
message: "VM Disk in GB:",
|
|
338
|
-
default: "20",
|
|
339
|
-
validate: (v) => {
|
|
340
|
-
const n = Number(v);
|
|
341
|
-
if (!Number.isInteger(n) || n < 10 || n > 2048) return "Must be a whole number between 10 and 2048";
|
|
342
|
-
return true;
|
|
343
|
-
}
|
|
344
|
-
})
|
|
345
|
-
);
|
|
346
|
-
console.log(chalk4.bold("\n Configuration\n"));
|
|
347
|
-
const enableTunnel = await confirm({
|
|
348
|
-
message: "Enable Cloudflare tunnel for remote access?",
|
|
349
|
-
default: true
|
|
350
|
-
});
|
|
351
|
-
return {
|
|
352
|
-
anthropicKey,
|
|
353
|
-
openaiKey,
|
|
354
|
-
googleKey,
|
|
355
|
-
vmRam,
|
|
356
|
-
vmCpus,
|
|
357
|
-
vmDisk,
|
|
358
|
-
enableTunnel
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// src/core/platform.ts
|
|
363
|
-
import os from "os";
|
|
364
|
-
function detectPlatform() {
|
|
365
|
-
const platform = os.platform();
|
|
366
|
-
const arch = os.arch();
|
|
367
|
-
if (platform === "darwin") {
|
|
368
|
-
return {
|
|
369
|
-
os: "mac",
|
|
370
|
-
arch: arch === "arm64" ? "arm64" : "x64",
|
|
371
|
-
qemuBinary: "qemu-system-aarch64",
|
|
372
|
-
qemuCpuFlag: "-accel hvf -cpu host",
|
|
373
|
-
ubuntuImage: "jammy-server-cloudimg-arm64.img",
|
|
374
|
-
biosPath: "/opt/homebrew/share/qemu/edk2-aarch64-code.fd"
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
if (platform === "linux") {
|
|
378
|
-
return {
|
|
379
|
-
os: "linux",
|
|
380
|
-
arch: arch === "arm64" ? "arm64" : "x64",
|
|
381
|
-
qemuBinary: arch === "arm64" ? "qemu-system-aarch64" : "qemu-system-x86_64",
|
|
382
|
-
qemuCpuFlag: "-cpu host -enable-kvm",
|
|
383
|
-
ubuntuImage: arch === "arm64" ? "jammy-server-cloudimg-arm64.img" : "jammy-server-cloudimg-amd64.img",
|
|
384
|
-
biosPath: arch === "arm64" ? "/usr/share/qemu-efi-aarch64/QEMU_EFI.fd" : "/usr/share/OVMF/OVMF_CODE.fd"
|
|
147
|
+
var NEXUS_HOME, SECRET_PATTERNS, FORBIDDEN_KEY_CHARS, KEY_VALIDATORS, DlpViolation, HMAC_PATH, AUDIT_PATH, MAX_AUDIT_SIZE, SCRUB_KEYS;
|
|
148
|
+
var init_dlp = __esm({
|
|
149
|
+
"src/core/dlp.ts"() {
|
|
150
|
+
"use strict";
|
|
151
|
+
NEXUS_HOME = path.join(process.env.HOME || "~", ".buildwithnexus");
|
|
152
|
+
SECRET_PATTERNS = [
|
|
153
|
+
/sk-ant-api03-[A-Za-z0-9_-]{20,}/g,
|
|
154
|
+
// Anthropic API key
|
|
155
|
+
/sk-[A-Za-z0-9]{20,}/g,
|
|
156
|
+
// OpenAI API key
|
|
157
|
+
/AIza[A-Za-z0-9_-]{35}/g
|
|
158
|
+
// Google AI API key
|
|
159
|
+
];
|
|
160
|
+
FORBIDDEN_KEY_CHARS = /[\n\r\t'"\\`${}();&|<>!#%^]/;
|
|
161
|
+
KEY_VALIDATORS = {
|
|
162
|
+
ANTHROPIC_API_KEY: /^sk-ant-[A-Za-z0-9_-]{20,}$/,
|
|
163
|
+
OPENAI_API_KEY: /^sk-[A-Za-z0-9_-]{20,}$/,
|
|
164
|
+
GOOGLE_API_KEY: /^AIza[A-Za-z0-9_-]{35,}$/,
|
|
165
|
+
NEXUS_MASTER_SECRET: /^[A-Za-z0-9_-]{20,64}$/
|
|
385
166
|
};
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
qemuBinary: "qemu-system-x86_64",
|
|
392
|
-
qemuCpuFlag: "-cpu qemu64",
|
|
393
|
-
ubuntuImage: "jammy-server-cloudimg-amd64.img",
|
|
394
|
-
biosPath: "C:\\Program Files\\qemu\\share\\edk2-x86_64-code.fd"
|
|
167
|
+
DlpViolation = class extends Error {
|
|
168
|
+
constructor(message) {
|
|
169
|
+
super(message);
|
|
170
|
+
this.name = "DlpViolation";
|
|
171
|
+
}
|
|
395
172
|
};
|
|
173
|
+
HMAC_PATH = path.join(NEXUS_HOME, ".keys.hmac");
|
|
174
|
+
AUDIT_PATH = path.join(NEXUS_HOME, "audit.log");
|
|
175
|
+
MAX_AUDIT_SIZE = 10 * 1024 * 1024;
|
|
176
|
+
SCRUB_KEYS = [
|
|
177
|
+
"ANTHROPIC_API_KEY",
|
|
178
|
+
"OPENAI_API_KEY",
|
|
179
|
+
"GOOGLE_API_KEY",
|
|
180
|
+
"NEXUS_MASTER_SECRET",
|
|
181
|
+
"NEXUS_SECRET",
|
|
182
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
183
|
+
"AWS_SESSION_TOKEN",
|
|
184
|
+
"GITHUB_TOKEN",
|
|
185
|
+
"GH_TOKEN",
|
|
186
|
+
"NPM_TOKEN",
|
|
187
|
+
"DOCKER_PASSWORD",
|
|
188
|
+
"CI_JOB_TOKEN"
|
|
189
|
+
];
|
|
396
190
|
}
|
|
397
|
-
|
|
398
|
-
}
|
|
191
|
+
});
|
|
399
192
|
|
|
400
193
|
// src/core/secrets.ts
|
|
194
|
+
var secrets_exports = {};
|
|
195
|
+
__export(secrets_exports, {
|
|
196
|
+
CONFIG_PATH: () => CONFIG_PATH,
|
|
197
|
+
KEYS_PATH: () => KEYS_PATH,
|
|
198
|
+
NEXUS_HOME: () => NEXUS_HOME2,
|
|
199
|
+
ensureHome: () => ensureHome,
|
|
200
|
+
generateMasterSecret: () => generateMasterSecret,
|
|
201
|
+
loadConfig: () => loadConfig,
|
|
202
|
+
loadKeys: () => loadKeys,
|
|
203
|
+
maskKey: () => maskKey,
|
|
204
|
+
saveConfig: () => saveConfig,
|
|
205
|
+
saveKeys: () => saveKeys
|
|
206
|
+
});
|
|
401
207
|
import fs2 from "fs";
|
|
402
208
|
import path2 from "path";
|
|
403
209
|
import crypto2 from "crypto";
|
|
404
|
-
var NEXUS_HOME2 = path2.join(process.env.HOME || "~", ".buildwithnexus");
|
|
405
|
-
var CONFIG_PATH = path2.join(NEXUS_HOME2, "config.json");
|
|
406
|
-
var KEYS_PATH = path2.join(NEXUS_HOME2, ".env.keys");
|
|
407
210
|
function ensureHome() {
|
|
408
211
|
fs2.mkdirSync(NEXUS_HOME2, { recursive: true, mode: 448 });
|
|
409
212
|
fs2.mkdirSync(path2.join(NEXUS_HOME2, "vm", "images"), { recursive: true, mode: 448 });
|
|
@@ -455,18 +258,36 @@ function maskKey(key) {
|
|
|
455
258
|
const reveal = Math.min(4, Math.floor(key.length * 0.1));
|
|
456
259
|
return key.slice(0, reveal) + "..." + key.slice(-reveal);
|
|
457
260
|
}
|
|
261
|
+
var NEXUS_HOME2, CONFIG_PATH, KEYS_PATH;
|
|
262
|
+
var init_secrets = __esm({
|
|
263
|
+
"src/core/secrets.ts"() {
|
|
264
|
+
"use strict";
|
|
265
|
+
init_dlp();
|
|
266
|
+
NEXUS_HOME2 = path2.join(process.env.HOME || "~", ".buildwithnexus");
|
|
267
|
+
CONFIG_PATH = path2.join(NEXUS_HOME2, "config.json");
|
|
268
|
+
KEYS_PATH = path2.join(NEXUS_HOME2, ".env.keys");
|
|
269
|
+
}
|
|
270
|
+
});
|
|
458
271
|
|
|
459
272
|
// src/core/qemu.ts
|
|
273
|
+
var qemu_exports = {};
|
|
274
|
+
__export(qemu_exports, {
|
|
275
|
+
createDisk: () => createDisk,
|
|
276
|
+
downloadImage: () => downloadImage,
|
|
277
|
+
getVmPid: () => getVmPid,
|
|
278
|
+
installQemu: () => installQemu,
|
|
279
|
+
isQemuInstalled: () => isQemuInstalled,
|
|
280
|
+
isVmRunning: () => isVmRunning,
|
|
281
|
+
launchVm: () => launchVm,
|
|
282
|
+
resolvePortConflicts: () => resolvePortConflicts,
|
|
283
|
+
stopVm: () => stopVm
|
|
284
|
+
});
|
|
460
285
|
import fs3 from "fs";
|
|
461
286
|
import net from "net";
|
|
462
287
|
import path3 from "path";
|
|
463
288
|
import { execa } from "execa";
|
|
464
289
|
import { select } from "@inquirer/prompts";
|
|
465
290
|
import chalk5 from "chalk";
|
|
466
|
-
var VM_DIR = path3.join(NEXUS_HOME2, "vm");
|
|
467
|
-
var IMAGES_DIR = path3.join(VM_DIR, "images");
|
|
468
|
-
var PID_FILE = path3.join(VM_DIR, "qemu.pid");
|
|
469
|
-
var UBUNTU_BASE_URL = "https://cloud-images.ubuntu.com/jammy/current";
|
|
470
291
|
async function isQemuInstalled(platform) {
|
|
471
292
|
try {
|
|
472
293
|
await execa(platform.qemuBinary, ["--version"], { env: scrubEnv() });
|
|
@@ -659,18 +480,36 @@ function stopVm() {
|
|
|
659
480
|
function getVmPid() {
|
|
660
481
|
return readValidPid();
|
|
661
482
|
}
|
|
483
|
+
var VM_DIR, IMAGES_DIR, PID_FILE, UBUNTU_BASE_URL;
|
|
484
|
+
var init_qemu = __esm({
|
|
485
|
+
"src/core/qemu.ts"() {
|
|
486
|
+
"use strict";
|
|
487
|
+
init_secrets();
|
|
488
|
+
init_dlp();
|
|
489
|
+
VM_DIR = path3.join(NEXUS_HOME2, "vm");
|
|
490
|
+
IMAGES_DIR = path3.join(VM_DIR, "images");
|
|
491
|
+
PID_FILE = path3.join(VM_DIR, "qemu.pid");
|
|
492
|
+
UBUNTU_BASE_URL = "https://cloud-images.ubuntu.com/jammy/current";
|
|
493
|
+
}
|
|
494
|
+
});
|
|
662
495
|
|
|
663
496
|
// src/core/ssh.ts
|
|
497
|
+
var ssh_exports = {};
|
|
498
|
+
__export(ssh_exports, {
|
|
499
|
+
addSshConfig: () => addSshConfig,
|
|
500
|
+
generateSshKey: () => generateSshKey,
|
|
501
|
+
getKeyPath: () => getKeyPath,
|
|
502
|
+
getPubKey: () => getPubKey,
|
|
503
|
+
openInteractiveSsh: () => openInteractiveSsh,
|
|
504
|
+
sshExec: () => sshExec,
|
|
505
|
+
sshUploadFile: () => sshUploadFile,
|
|
506
|
+
waitForSsh: () => waitForSsh
|
|
507
|
+
});
|
|
664
508
|
import fs4 from "fs";
|
|
665
509
|
import path4 from "path";
|
|
666
510
|
import crypto3 from "crypto";
|
|
667
511
|
import { execa as execa2 } from "execa";
|
|
668
512
|
import { NodeSSH } from "node-ssh";
|
|
669
|
-
var SSH_DIR = path4.join(NEXUS_HOME2, "ssh");
|
|
670
|
-
var SSH_KEY = path4.join(SSH_DIR, "id_nexus_vm");
|
|
671
|
-
var SSH_PUB_KEY = path4.join(SSH_DIR, "id_nexus_vm.pub");
|
|
672
|
-
var KNOWN_HOSTS = path4.join(SSH_DIR, "known_hosts_nexus_vm");
|
|
673
|
-
var PINNED_HOST_KEY = path4.join(SSH_DIR, "vm_host_key.pin");
|
|
674
513
|
function getHostVerifier() {
|
|
675
514
|
if (!fs4.existsSync(PINNED_HOST_KEY)) {
|
|
676
515
|
return (key) => {
|
|
@@ -688,6 +527,9 @@ function getHostVerifier() {
|
|
|
688
527
|
return match;
|
|
689
528
|
};
|
|
690
529
|
}
|
|
530
|
+
function getKeyPath() {
|
|
531
|
+
return SSH_KEY;
|
|
532
|
+
}
|
|
691
533
|
function getPubKey() {
|
|
692
534
|
return fs4.readFileSync(SSH_PUB_KEY, "utf-8").trim();
|
|
693
535
|
}
|
|
@@ -776,14 +618,256 @@ async function sshUploadFile(port, localPath, remotePath) {
|
|
|
776
618
|
privateKeyPath: SSH_KEY,
|
|
777
619
|
hostVerifier: getHostVerifier()
|
|
778
620
|
});
|
|
779
|
-
await ssh.putFile(localPath, remotePath);
|
|
780
|
-
ssh.dispose();
|
|
621
|
+
await ssh.putFile(localPath, remotePath);
|
|
622
|
+
ssh.dispose();
|
|
623
|
+
}
|
|
624
|
+
async function openInteractiveSsh(port) {
|
|
625
|
+
await execa2("ssh", ["nexus-vm"], { stdio: "inherit", env: scrubEnv() });
|
|
626
|
+
}
|
|
627
|
+
var SSH_DIR, SSH_KEY, SSH_PUB_KEY, KNOWN_HOSTS, PINNED_HOST_KEY;
|
|
628
|
+
var init_ssh = __esm({
|
|
629
|
+
"src/core/ssh.ts"() {
|
|
630
|
+
"use strict";
|
|
631
|
+
init_secrets();
|
|
632
|
+
init_dlp();
|
|
633
|
+
SSH_DIR = path4.join(NEXUS_HOME2, "ssh");
|
|
634
|
+
SSH_KEY = path4.join(SSH_DIR, "id_nexus_vm");
|
|
635
|
+
SSH_PUB_KEY = path4.join(SSH_DIR, "id_nexus_vm.pub");
|
|
636
|
+
KNOWN_HOSTS = path4.join(SSH_DIR, "known_hosts_nexus_vm");
|
|
637
|
+
PINNED_HOST_KEY = path4.join(SSH_DIR, "vm_host_key.pin");
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
// src/cli.ts
|
|
642
|
+
import { Command as Command13 } from "commander";
|
|
643
|
+
|
|
644
|
+
// src/commands/init.ts
|
|
645
|
+
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
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/ui/spinner.ts
|
|
694
|
+
import ora from "ora";
|
|
695
|
+
import chalk2 from "chalk";
|
|
696
|
+
function createSpinner(text) {
|
|
697
|
+
return ora({ text, color: "cyan", spinner: "dots" });
|
|
698
|
+
}
|
|
699
|
+
function succeed(spinner, text) {
|
|
700
|
+
spinner.succeed(chalk2.green(text));
|
|
701
|
+
}
|
|
702
|
+
function fail(spinner, text) {
|
|
703
|
+
spinner.fail(chalk2.red(text));
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// src/ui/logger.ts
|
|
707
|
+
import chalk3 from "chalk";
|
|
708
|
+
var log = {
|
|
709
|
+
step(msg) {
|
|
710
|
+
console.log(chalk3.cyan(" \u2192 ") + msg);
|
|
711
|
+
},
|
|
712
|
+
success(msg) {
|
|
713
|
+
console.log(chalk3.green(" \u2713 ") + msg);
|
|
714
|
+
},
|
|
715
|
+
error(msg) {
|
|
716
|
+
console.error(chalk3.red(" \u2717 ") + msg);
|
|
717
|
+
},
|
|
718
|
+
warn(msg) {
|
|
719
|
+
console.log(chalk3.yellow(" \u26A0 ") + msg);
|
|
720
|
+
},
|
|
721
|
+
dim(msg) {
|
|
722
|
+
console.log(chalk3.dim(" " + msg));
|
|
723
|
+
},
|
|
724
|
+
detail(label, value) {
|
|
725
|
+
console.log(chalk3.dim(" " + label + ": ") + value);
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
// src/ui/prompts.ts
|
|
730
|
+
init_dlp();
|
|
731
|
+
import { input, confirm, password } from "@inquirer/prompts";
|
|
732
|
+
import chalk4 from "chalk";
|
|
733
|
+
async function promptInitConfig() {
|
|
734
|
+
console.log(chalk4.bold("\n API Keys\n"));
|
|
735
|
+
const anthropicKey = await password({
|
|
736
|
+
message: "Anthropic API key (required):",
|
|
737
|
+
mask: "*",
|
|
738
|
+
validate: (val) => {
|
|
739
|
+
if (!val) return "API key is required";
|
|
740
|
+
if (!val.startsWith("sk-ant-")) return "Must start with sk-ant-";
|
|
741
|
+
try {
|
|
742
|
+
validateKeyValue("ANTHROPIC_API_KEY", val);
|
|
743
|
+
} catch (err) {
|
|
744
|
+
if (err instanceof DlpViolation) return err.message;
|
|
745
|
+
}
|
|
746
|
+
return true;
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
const openaiKey = await password({
|
|
750
|
+
message: "OpenAI API key (optional, press Enter to skip):",
|
|
751
|
+
mask: "*",
|
|
752
|
+
validate: (val) => {
|
|
753
|
+
if (!val) return true;
|
|
754
|
+
try {
|
|
755
|
+
validateKeyValue("OPENAI_API_KEY", val);
|
|
756
|
+
} catch (err) {
|
|
757
|
+
if (err instanceof DlpViolation) return err.message;
|
|
758
|
+
}
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
const googleKey = await password({
|
|
763
|
+
message: "Google AI API key (optional, press Enter to skip):",
|
|
764
|
+
mask: "*",
|
|
765
|
+
validate: (val) => {
|
|
766
|
+
if (!val) return true;
|
|
767
|
+
try {
|
|
768
|
+
validateKeyValue("GOOGLE_API_KEY", val);
|
|
769
|
+
} catch (err) {
|
|
770
|
+
if (err instanceof DlpViolation) return err.message;
|
|
771
|
+
}
|
|
772
|
+
return true;
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
console.log(chalk4.bold("\n VM Resources\n"));
|
|
776
|
+
const vmRam = Number(
|
|
777
|
+
await input({
|
|
778
|
+
message: "VM RAM in GB:",
|
|
779
|
+
default: "4",
|
|
780
|
+
validate: (v) => {
|
|
781
|
+
const n = Number(v);
|
|
782
|
+
if (!Number.isInteger(n) || n < 2 || n > 256) return "Must be a whole number between 2 and 256";
|
|
783
|
+
return true;
|
|
784
|
+
}
|
|
785
|
+
})
|
|
786
|
+
);
|
|
787
|
+
const vmCpus = Number(
|
|
788
|
+
await input({
|
|
789
|
+
message: "VM CPUs:",
|
|
790
|
+
default: "2",
|
|
791
|
+
validate: (v) => {
|
|
792
|
+
const n = Number(v);
|
|
793
|
+
if (!Number.isInteger(n) || n < 1 || n > 64) return "Must be a whole number between 1 and 64";
|
|
794
|
+
return true;
|
|
795
|
+
}
|
|
796
|
+
})
|
|
797
|
+
);
|
|
798
|
+
const vmDisk = Number(
|
|
799
|
+
await input({
|
|
800
|
+
message: "VM Disk in GB:",
|
|
801
|
+
default: "20",
|
|
802
|
+
validate: (v) => {
|
|
803
|
+
const n = Number(v);
|
|
804
|
+
if (!Number.isInteger(n) || n < 10 || n > 2048) return "Must be a whole number between 10 and 2048";
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
})
|
|
808
|
+
);
|
|
809
|
+
console.log(chalk4.bold("\n Configuration\n"));
|
|
810
|
+
const enableTunnel = await confirm({
|
|
811
|
+
message: "Enable Cloudflare tunnel for remote access?",
|
|
812
|
+
default: true
|
|
813
|
+
});
|
|
814
|
+
return {
|
|
815
|
+
anthropicKey,
|
|
816
|
+
openaiKey,
|
|
817
|
+
googleKey,
|
|
818
|
+
vmRam,
|
|
819
|
+
vmCpus,
|
|
820
|
+
vmDisk,
|
|
821
|
+
enableTunnel
|
|
822
|
+
};
|
|
781
823
|
}
|
|
782
|
-
|
|
783
|
-
|
|
824
|
+
|
|
825
|
+
// src/core/platform.ts
|
|
826
|
+
import os from "os";
|
|
827
|
+
function detectPlatform() {
|
|
828
|
+
const platform = os.platform();
|
|
829
|
+
const arch = os.arch();
|
|
830
|
+
if (platform === "darwin") {
|
|
831
|
+
return {
|
|
832
|
+
os: "mac",
|
|
833
|
+
arch: arch === "arm64" ? "arm64" : "x64",
|
|
834
|
+
qemuBinary: "qemu-system-aarch64",
|
|
835
|
+
qemuCpuFlag: "-accel hvf -cpu host",
|
|
836
|
+
ubuntuImage: "jammy-server-cloudimg-arm64.img",
|
|
837
|
+
biosPath: "/opt/homebrew/share/qemu/edk2-aarch64-code.fd"
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
if (platform === "linux") {
|
|
841
|
+
return {
|
|
842
|
+
os: "linux",
|
|
843
|
+
arch: arch === "arm64" ? "arm64" : "x64",
|
|
844
|
+
qemuBinary: arch === "arm64" ? "qemu-system-aarch64" : "qemu-system-x86_64",
|
|
845
|
+
qemuCpuFlag: "-cpu host -enable-kvm",
|
|
846
|
+
ubuntuImage: arch === "arm64" ? "jammy-server-cloudimg-arm64.img" : "jammy-server-cloudimg-amd64.img",
|
|
847
|
+
biosPath: arch === "arm64" ? "/usr/share/qemu-efi-aarch64/QEMU_EFI.fd" : "/usr/share/OVMF/OVMF_CODE.fd"
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
if (platform === "win32") {
|
|
851
|
+
return {
|
|
852
|
+
os: "windows",
|
|
853
|
+
arch: "x64",
|
|
854
|
+
qemuBinary: "qemu-system-x86_64",
|
|
855
|
+
qemuCpuFlag: "-cpu qemu64",
|
|
856
|
+
ubuntuImage: "jammy-server-cloudimg-amd64.img",
|
|
857
|
+
biosPath: "C:\\Program Files\\qemu\\share\\edk2-x86_64-code.fd"
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
throw new Error(`Unsupported platform: ${platform} ${arch}`);
|
|
784
861
|
}
|
|
785
862
|
|
|
863
|
+
// src/commands/init.ts
|
|
864
|
+
init_secrets();
|
|
865
|
+
init_qemu();
|
|
866
|
+
init_ssh();
|
|
867
|
+
|
|
786
868
|
// src/core/cloudinit.ts
|
|
869
|
+
init_secrets();
|
|
870
|
+
init_dlp();
|
|
787
871
|
import fs5 from "fs";
|
|
788
872
|
import path5 from "path";
|
|
789
873
|
import ejs from "ejs";
|
|
@@ -877,6 +961,7 @@ async function createCloudInitIso(userDataPath) {
|
|
|
877
961
|
}
|
|
878
962
|
|
|
879
963
|
// src/core/health.ts
|
|
964
|
+
init_ssh();
|
|
880
965
|
async function checkHealth(port, vmRunning) {
|
|
881
966
|
const status = {
|
|
882
967
|
vmRunning,
|
|
@@ -937,6 +1022,8 @@ async function waitForCloudInit(port, timeoutMs = 9e5) {
|
|
|
937
1022
|
}
|
|
938
1023
|
|
|
939
1024
|
// src/core/tunnel.ts
|
|
1025
|
+
init_ssh();
|
|
1026
|
+
init_dlp();
|
|
940
1027
|
var CLOUDFLARED_VERSION = "2024.12.2";
|
|
941
1028
|
var CLOUDFLARED_SHA256 = {
|
|
942
1029
|
amd64: "40ec9a0f5b58e3b04183aaf01c4ddd4dbc6af39b0f06be4b7ce8b1011d0a07ab",
|
|
@@ -984,6 +1071,8 @@ async function stopTunnel(sshPort) {
|
|
|
984
1071
|
}
|
|
985
1072
|
|
|
986
1073
|
// src/commands/init.ts
|
|
1074
|
+
init_dlp();
|
|
1075
|
+
init_ssh();
|
|
987
1076
|
import fs6 from "fs";
|
|
988
1077
|
import path6 from "path";
|
|
989
1078
|
import os2 from "os";
|
|
@@ -1105,15 +1194,14 @@ var initCommand = new Command("init").description("Scaffold and launch a new NEX
|
|
|
1105
1194
|
spinner.start();
|
|
1106
1195
|
await sshUploadFile(config.sshPort, tarballPath, "/tmp/nexus-release.tar.gz");
|
|
1107
1196
|
succeed(spinner, "Tarball uploaded");
|
|
1108
|
-
spinner = createSpinner("
|
|
1197
|
+
spinner = createSpinner("Staging API keys...");
|
|
1109
1198
|
spinner.start();
|
|
1110
1199
|
const keysContent = Object.entries(keys).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
|
|
1111
1200
|
const tmpKeysPath = path6.join(os2.tmpdir(), `.nexus-keys-${crypto4.randomBytes(8).toString("hex")}`);
|
|
1112
1201
|
fs6.writeFileSync(tmpKeysPath, keysContent, { mode: 384 });
|
|
1113
1202
|
try {
|
|
1114
|
-
await
|
|
1115
|
-
await
|
|
1116
|
-
await sshExec(config.sshPort, "chown nexus:nexus /home/nexus/.nexus/.env.keys && chmod 600 /home/nexus/.nexus/.env.keys");
|
|
1203
|
+
await sshUploadFile(config.sshPort, tmpKeysPath, "/tmp/.nexus-env-keys");
|
|
1204
|
+
await sshExec(config.sshPort, "chmod 600 /tmp/.nexus-env-keys");
|
|
1117
1205
|
} finally {
|
|
1118
1206
|
try {
|
|
1119
1207
|
fs6.writeFileSync(tmpKeysPath, "0".repeat(keysContent.length));
|
|
@@ -1121,7 +1209,7 @@ var initCommand = new Command("init").description("Scaffold and launch a new NEX
|
|
|
1121
1209
|
} catch {
|
|
1122
1210
|
}
|
|
1123
1211
|
}
|
|
1124
|
-
succeed(spinner, "API keys
|
|
1212
|
+
succeed(spinner, "API keys staged");
|
|
1125
1213
|
spinner = createSpinner("Cloud-init provisioning (extracting NEXUS, building Docker, installing deps)...");
|
|
1126
1214
|
spinner.start();
|
|
1127
1215
|
const cloudInitDone = await waitForCloudInit(config.sshPort);
|
|
@@ -1131,6 +1219,10 @@ var initCommand = new Command("init").description("Scaffold and launch a new NEX
|
|
|
1131
1219
|
process.exit(1);
|
|
1132
1220
|
}
|
|
1133
1221
|
succeed(spinner, "VM fully provisioned");
|
|
1222
|
+
spinner = createSpinner("Delivering API keys...");
|
|
1223
|
+
spinner.start();
|
|
1224
|
+
await sshExec(config.sshPort, "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");
|
|
1225
|
+
succeed(spinner, "API keys delivered securely");
|
|
1134
1226
|
showPhase(8, TOTAL_PHASES, "NEXUS Server Startup");
|
|
1135
1227
|
spinner = createSpinner("Waiting for NEXUS server...");
|
|
1136
1228
|
spinner.start();
|
|
@@ -1173,6 +1265,10 @@ var initCommand = new Command("init").description("Scaffold and launch a new NEX
|
|
|
1173
1265
|
|
|
1174
1266
|
// src/commands/start.ts
|
|
1175
1267
|
import { Command as Command2 } from "commander";
|
|
1268
|
+
init_secrets();
|
|
1269
|
+
init_qemu();
|
|
1270
|
+
init_ssh();
|
|
1271
|
+
init_secrets();
|
|
1176
1272
|
import path7 from "path";
|
|
1177
1273
|
var startCommand = new Command2("start").description("Start the NEXUS runtime").action(async () => {
|
|
1178
1274
|
const config = loadConfig();
|
|
@@ -1227,6 +1323,9 @@ var startCommand = new Command2("start").description("Start the NEXUS runtime").
|
|
|
1227
1323
|
|
|
1228
1324
|
// src/commands/stop.ts
|
|
1229
1325
|
import { Command as Command3 } from "commander";
|
|
1326
|
+
init_secrets();
|
|
1327
|
+
init_qemu();
|
|
1328
|
+
init_ssh();
|
|
1230
1329
|
var stopCommand = new Command3("stop").description("Gracefully shut down the NEXUS runtime").action(async () => {
|
|
1231
1330
|
const config = loadConfig();
|
|
1232
1331
|
if (!config) {
|
|
@@ -1263,6 +1362,8 @@ var stopCommand = new Command3("stop").description("Gracefully shut down the NEX
|
|
|
1263
1362
|
// src/commands/status.ts
|
|
1264
1363
|
import { Command as Command4 } from "commander";
|
|
1265
1364
|
import chalk6 from "chalk";
|
|
1365
|
+
init_secrets();
|
|
1366
|
+
init_qemu();
|
|
1266
1367
|
var statusCommand = new Command4("status").description("Check NEXUS runtime health").option("--json", "Output as JSON").action(async (opts) => {
|
|
1267
1368
|
const config = loadConfig();
|
|
1268
1369
|
if (!config) {
|
|
@@ -1294,6 +1395,8 @@ var statusCommand = new Command4("status").description("Check NEXUS runtime heal
|
|
|
1294
1395
|
import { Command as Command5 } from "commander";
|
|
1295
1396
|
import chalk7 from "chalk";
|
|
1296
1397
|
import fs7 from "fs";
|
|
1398
|
+
init_qemu();
|
|
1399
|
+
init_secrets();
|
|
1297
1400
|
import path8 from "path";
|
|
1298
1401
|
import { execa as execa4 } from "execa";
|
|
1299
1402
|
var doctorCommand = new Command5("doctor").description("Diagnose NEXUS runtime environment").action(async () => {
|
|
@@ -1361,6 +1464,10 @@ var doctorCommand = new Command5("doctor").description("Diagnose NEXUS runtime e
|
|
|
1361
1464
|
|
|
1362
1465
|
// src/commands/logs.ts
|
|
1363
1466
|
import { Command as Command6 } from "commander";
|
|
1467
|
+
init_secrets();
|
|
1468
|
+
init_qemu();
|
|
1469
|
+
init_ssh();
|
|
1470
|
+
init_dlp();
|
|
1364
1471
|
import { execa as execa5 } from "execa";
|
|
1365
1472
|
var logsCommand = new Command6("logs").description("View NEXUS server logs").option("-f, --follow", "Follow log output").option("-n, --lines <n>", "Number of lines to show", "50").action(async (opts) => {
|
|
1366
1473
|
const config = loadConfig();
|
|
@@ -1399,6 +1506,9 @@ import { Command as Command7 } from "commander";
|
|
|
1399
1506
|
import path9 from "path";
|
|
1400
1507
|
import fs8 from "fs";
|
|
1401
1508
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1509
|
+
init_secrets();
|
|
1510
|
+
init_qemu();
|
|
1511
|
+
init_ssh();
|
|
1402
1512
|
function getReleaseTarball2() {
|
|
1403
1513
|
const dir = path9.dirname(fileURLToPath2(import.meta.url));
|
|
1404
1514
|
const tarballPath = path9.join(dir, "nexus-release.tar.gz");
|
|
@@ -1457,6 +1567,8 @@ import { Command as Command8 } from "commander";
|
|
|
1457
1567
|
import chalk8 from "chalk";
|
|
1458
1568
|
import fs9 from "fs";
|
|
1459
1569
|
import { input as input2 } from "@inquirer/prompts";
|
|
1570
|
+
init_secrets();
|
|
1571
|
+
init_qemu();
|
|
1460
1572
|
import path10 from "path";
|
|
1461
1573
|
var destroyCommand = new Command8("destroy").description("Remove NEXUS VM and all data").option("--force", "Skip confirmation").action(async (opts) => {
|
|
1462
1574
|
const config = loadConfig();
|
|
@@ -1511,6 +1623,8 @@ var destroyCommand = new Command8("destroy").description("Remove NEXUS VM and al
|
|
|
1511
1623
|
import { Command as Command9 } from "commander";
|
|
1512
1624
|
import { password as password2 } from "@inquirer/prompts";
|
|
1513
1625
|
import chalk9 from "chalk";
|
|
1626
|
+
init_secrets();
|
|
1627
|
+
init_dlp();
|
|
1514
1628
|
var keysCommand = new Command9("keys").description("Manage API keys");
|
|
1515
1629
|
keysCommand.command("list").description("Show configured API keys (masked)").action(() => {
|
|
1516
1630
|
const keys = loadKeys();
|
|
@@ -1567,6 +1681,9 @@ keysCommand.command("set <key>").description("Set or update an API key (e.g. ANT
|
|
|
1567
1681
|
|
|
1568
1682
|
// src/commands/ssh.ts
|
|
1569
1683
|
import { Command as Command10 } from "commander";
|
|
1684
|
+
init_secrets();
|
|
1685
|
+
init_qemu();
|
|
1686
|
+
init_ssh();
|
|
1570
1687
|
var sshCommand = new Command10("ssh").description("Open an SSH session into the NEXUS VM").action(async () => {
|
|
1571
1688
|
const config = loadConfig();
|
|
1572
1689
|
if (!config) {
|
|
@@ -1585,6 +1702,10 @@ var sshCommand = new Command10("ssh").description("Open an SSH session into the
|
|
|
1585
1702
|
import { Command as Command11 } from "commander";
|
|
1586
1703
|
import chalk10 from "chalk";
|
|
1587
1704
|
import { input as input3 } from "@inquirer/prompts";
|
|
1705
|
+
init_secrets();
|
|
1706
|
+
init_qemu();
|
|
1707
|
+
init_ssh();
|
|
1708
|
+
init_dlp();
|
|
1588
1709
|
var COS_PREFIX = chalk10.bold.cyan(" Chief of Staff");
|
|
1589
1710
|
var YOU_PREFIX = chalk10.bold.white(" You");
|
|
1590
1711
|
var DIVIDER = chalk10.dim(" " + "\u2500".repeat(56));
|
|
@@ -1699,8 +1820,478 @@ var brainstormCommand = new Command11("brainstorm").description("Brainstorm an i
|
|
|
1699
1820
|
}
|
|
1700
1821
|
});
|
|
1701
1822
|
|
|
1823
|
+
// src/commands/shell.ts
|
|
1824
|
+
import { Command as Command12 } from "commander";
|
|
1825
|
+
import chalk13 from "chalk";
|
|
1826
|
+
init_secrets();
|
|
1827
|
+
init_qemu();
|
|
1828
|
+
init_ssh();
|
|
1829
|
+
init_dlp();
|
|
1830
|
+
|
|
1831
|
+
// src/ui/repl.ts
|
|
1832
|
+
init_secrets();
|
|
1833
|
+
import readline from "readline";
|
|
1834
|
+
import fs10 from "fs";
|
|
1835
|
+
import path11 from "path";
|
|
1836
|
+
import chalk11 from "chalk";
|
|
1837
|
+
var HISTORY_FILE = path11.join(NEXUS_HOME2, "shell_history");
|
|
1838
|
+
var MAX_HISTORY = 1e3;
|
|
1839
|
+
var Repl = class {
|
|
1840
|
+
rl = null;
|
|
1841
|
+
commands = /* @__PURE__ */ new Map();
|
|
1842
|
+
onMessage;
|
|
1843
|
+
history = [];
|
|
1844
|
+
constructor(onMessage) {
|
|
1845
|
+
this.onMessage = onMessage;
|
|
1846
|
+
this.loadHistory();
|
|
1847
|
+
}
|
|
1848
|
+
registerCommand(cmd) {
|
|
1849
|
+
this.commands.set(cmd.name, cmd);
|
|
1850
|
+
}
|
|
1851
|
+
loadHistory() {
|
|
1852
|
+
try {
|
|
1853
|
+
if (fs10.existsSync(HISTORY_FILE)) {
|
|
1854
|
+
this.history = fs10.readFileSync(HISTORY_FILE, "utf-8").split("\n").filter(Boolean).slice(-MAX_HISTORY);
|
|
1855
|
+
}
|
|
1856
|
+
} catch {
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
saveHistory() {
|
|
1860
|
+
try {
|
|
1861
|
+
const dir = path11.dirname(HISTORY_FILE);
|
|
1862
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
1863
|
+
fs10.writeFileSync(HISTORY_FILE, this.history.slice(-MAX_HISTORY).join("\n") + "\n", { mode: 384 });
|
|
1864
|
+
} catch {
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
async start() {
|
|
1868
|
+
this.rl = readline.createInterface({
|
|
1869
|
+
input: process.stdin,
|
|
1870
|
+
output: process.stdout,
|
|
1871
|
+
prompt: chalk11.bold.cyan("nexus") + chalk11.dim(" \u276F "),
|
|
1872
|
+
history: this.history,
|
|
1873
|
+
historySize: MAX_HISTORY,
|
|
1874
|
+
terminal: true
|
|
1875
|
+
});
|
|
1876
|
+
this.rl.prompt();
|
|
1877
|
+
this.rl.on("line", async (line) => {
|
|
1878
|
+
const trimmed = line.trim();
|
|
1879
|
+
if (!trimmed) {
|
|
1880
|
+
this.rl?.prompt();
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
this.history.push(trimmed);
|
|
1884
|
+
if (trimmed.startsWith("/")) {
|
|
1885
|
+
const parts = trimmed.slice(1).split(/\s+/);
|
|
1886
|
+
const cmdName = parts[0].toLowerCase();
|
|
1887
|
+
if (cmdName === "help") {
|
|
1888
|
+
this.showHelp();
|
|
1889
|
+
this.rl?.prompt();
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1892
|
+
if (cmdName === "exit" || cmdName === "quit") {
|
|
1893
|
+
this.stop();
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
const cmd = this.commands.get(cmdName);
|
|
1897
|
+
if (cmd) {
|
|
1898
|
+
try {
|
|
1899
|
+
await cmd.handler();
|
|
1900
|
+
} catch (err) {
|
|
1901
|
+
console.log(chalk11.red(` \u2717 Command failed: ${err.message}`));
|
|
1902
|
+
}
|
|
1903
|
+
this.rl?.prompt();
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
console.log(chalk11.yellow(` Unknown command: /${cmdName}. Type /help for available commands.`));
|
|
1907
|
+
this.rl?.prompt();
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1910
|
+
try {
|
|
1911
|
+
await this.onMessage(trimmed);
|
|
1912
|
+
} catch (err) {
|
|
1913
|
+
console.log(chalk11.red(` \u2717 ${err.message}`));
|
|
1914
|
+
}
|
|
1915
|
+
this.rl?.prompt();
|
|
1916
|
+
});
|
|
1917
|
+
this.rl.on("close", () => {
|
|
1918
|
+
this.saveHistory();
|
|
1919
|
+
console.log(chalk11.dim("\n Session ended."));
|
|
1920
|
+
process.exit(0);
|
|
1921
|
+
});
|
|
1922
|
+
this.rl.on("SIGINT", () => {
|
|
1923
|
+
this.stop();
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
showHelp() {
|
|
1927
|
+
console.log("");
|
|
1928
|
+
console.log(chalk11.bold(" Available Commands:"));
|
|
1929
|
+
console.log(chalk11.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
|
+
for (const [name, cmd] of this.commands) {
|
|
1931
|
+
console.log(` ${chalk11.cyan("/" + name.padEnd(14))} ${chalk11.dim(cmd.description)}`);
|
|
1932
|
+
}
|
|
1933
|
+
console.log(` ${chalk11.cyan("/help".padEnd(15))} ${chalk11.dim("Show this help message")}`);
|
|
1934
|
+
console.log(` ${chalk11.cyan("/exit".padEnd(15))} ${chalk11.dim("Exit the shell")}`);
|
|
1935
|
+
console.log(chalk11.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"));
|
|
1936
|
+
console.log(chalk11.dim(" Type anything else to chat with NEXUS"));
|
|
1937
|
+
console.log("");
|
|
1938
|
+
}
|
|
1939
|
+
write(text) {
|
|
1940
|
+
if (this.rl) {
|
|
1941
|
+
readline.clearLine(process.stdout, 0);
|
|
1942
|
+
readline.cursorTo(process.stdout, 0);
|
|
1943
|
+
console.log(text);
|
|
1944
|
+
this.rl.prompt(true);
|
|
1945
|
+
} else {
|
|
1946
|
+
console.log(text);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
stop() {
|
|
1950
|
+
this.saveHistory();
|
|
1951
|
+
if (this.rl) {
|
|
1952
|
+
this.rl.close();
|
|
1953
|
+
this.rl = null;
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
};
|
|
1957
|
+
|
|
1958
|
+
// src/core/event-stream.ts
|
|
1959
|
+
init_ssh();
|
|
1960
|
+
init_secrets();
|
|
1961
|
+
init_dlp();
|
|
1962
|
+
import chalk12 from "chalk";
|
|
1963
|
+
var ROLE_COLORS = {
|
|
1964
|
+
"Chief of Staff": chalk12.bold.cyan,
|
|
1965
|
+
"VP Engineering": chalk12.bold.blue,
|
|
1966
|
+
"VP Product": chalk12.bold.magenta,
|
|
1967
|
+
"Senior Engineer": chalk12.bold.green,
|
|
1968
|
+
"Engineer": chalk12.green,
|
|
1969
|
+
"QA Lead": chalk12.bold.yellow,
|
|
1970
|
+
"Security Engineer": chalk12.bold.red,
|
|
1971
|
+
"DevOps Engineer": chalk12.bold.hex("#FF8C00"),
|
|
1972
|
+
"default": chalk12.bold.white
|
|
1973
|
+
};
|
|
1974
|
+
function getColor(role) {
|
|
1975
|
+
if (!role) return ROLE_COLORS["default"];
|
|
1976
|
+
return ROLE_COLORS[role] || ROLE_COLORS["default"];
|
|
1977
|
+
}
|
|
1978
|
+
function formatEvent(event) {
|
|
1979
|
+
const color = getColor(event.role);
|
|
1980
|
+
const prefix = event.role ? color(` [${event.role}]`) : "";
|
|
1981
|
+
switch (event.type) {
|
|
1982
|
+
case "agent_thinking":
|
|
1983
|
+
return `${prefix} ${chalk12.dim("thinking...")}`;
|
|
1984
|
+
case "agent_response":
|
|
1985
|
+
return `${prefix} ${redact(event.content ?? "")}`;
|
|
1986
|
+
case "task_delegated":
|
|
1987
|
+
return `${prefix} ${chalk12.dim("\u2192")} delegated to ${chalk12.bold(event.target ?? "agent")}`;
|
|
1988
|
+
case "agent_complete":
|
|
1989
|
+
return `${prefix} ${chalk12.green("\u2713")} ${chalk12.dim("complete")}`;
|
|
1990
|
+
case "error":
|
|
1991
|
+
return ` ${chalk12.red("\u2717")} ${redact(event.content ?? "Unknown error")}`;
|
|
1992
|
+
case "heartbeat":
|
|
1993
|
+
return null;
|
|
1994
|
+
default:
|
|
1995
|
+
return null;
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
function parseSSEData(raw) {
|
|
1999
|
+
const events = [];
|
|
2000
|
+
const blocks = raw.split("\n\n");
|
|
2001
|
+
for (const block of blocks) {
|
|
2002
|
+
if (!block.trim()) continue;
|
|
2003
|
+
let data = "";
|
|
2004
|
+
for (const line of block.split("\n")) {
|
|
2005
|
+
if (line.startsWith("data: ")) {
|
|
2006
|
+
data += line.slice(6);
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
if (data) {
|
|
2010
|
+
try {
|
|
2011
|
+
events.push(JSON.parse(data));
|
|
2012
|
+
} catch {
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
return events;
|
|
2017
|
+
}
|
|
2018
|
+
var EventStream = class {
|
|
2019
|
+
active = false;
|
|
2020
|
+
lastId = "0";
|
|
2021
|
+
onEvent;
|
|
2022
|
+
pollInterval = null;
|
|
2023
|
+
constructor(onEvent) {
|
|
2024
|
+
this.onEvent = onEvent;
|
|
2025
|
+
}
|
|
2026
|
+
async start() {
|
|
2027
|
+
this.active = true;
|
|
2028
|
+
const config = loadConfig();
|
|
2029
|
+
if (!config) return;
|
|
2030
|
+
this.pollInterval = setInterval(async () => {
|
|
2031
|
+
if (!this.active) return;
|
|
2032
|
+
try {
|
|
2033
|
+
const { stdout, code } = await sshExec(
|
|
2034
|
+
config.sshPort,
|
|
2035
|
+
`curl -sf -H 'Last-Event-ID: ${this.lastId}' http://localhost:4200/events?timeout=1 2>/dev/null || true`
|
|
2036
|
+
);
|
|
2037
|
+
if (code === 0 && stdout.trim()) {
|
|
2038
|
+
const events = parseSSEData(stdout);
|
|
2039
|
+
for (const event of events) {
|
|
2040
|
+
if (event.id) this.lastId = event.id;
|
|
2041
|
+
this.onEvent(event);
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
} catch {
|
|
2045
|
+
}
|
|
2046
|
+
}, 2e3);
|
|
2047
|
+
}
|
|
2048
|
+
stop() {
|
|
2049
|
+
this.active = false;
|
|
2050
|
+
if (this.pollInterval) {
|
|
2051
|
+
clearInterval(this.pollInterval);
|
|
2052
|
+
this.pollInterval = null;
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
};
|
|
2056
|
+
|
|
2057
|
+
// src/commands/shell.ts
|
|
2058
|
+
async function sendMessage2(sshPort, message) {
|
|
2059
|
+
const payload = JSON.stringify({ message, source: "shell" });
|
|
2060
|
+
const escaped = shellEscape(payload);
|
|
2061
|
+
const { stdout, code } = await sshExec(
|
|
2062
|
+
sshPort,
|
|
2063
|
+
`curl -sf -X POST http://localhost:4200/message -H 'Content-Type: application/json' -d ${escaped}`
|
|
2064
|
+
);
|
|
2065
|
+
if (code !== 0) throw new Error("Server returned a non-zero exit code");
|
|
2066
|
+
try {
|
|
2067
|
+
const parsed = JSON.parse(stdout);
|
|
2068
|
+
return parsed.response ?? parsed.message ?? stdout;
|
|
2069
|
+
} catch {
|
|
2070
|
+
return stdout;
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
function showShellBanner(health) {
|
|
2074
|
+
const check = chalk13.green("\u2713");
|
|
2075
|
+
const cross = chalk13.red("\u2717");
|
|
2076
|
+
console.log("");
|
|
2077
|
+
console.log(chalk13.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"));
|
|
2078
|
+
console.log(chalk13.bold(" \u2551 ") + chalk13.bold.cyan("NEXUS Interactive Shell") + chalk13.bold(" \u2551"));
|
|
2079
|
+
console.log(chalk13.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"));
|
|
2080
|
+
console.log(chalk13.bold(" \u2551 ") + `${health.vmRunning ? check : cross} VM ${health.sshReady ? check : cross} SSH ${health.dockerReady ? check : cross} Docker ${health.serverHealthy ? check : cross} Engine`.padEnd(55) + chalk13.bold("\u2551"));
|
|
2081
|
+
if (health.tunnelUrl) {
|
|
2082
|
+
console.log(chalk13.bold(" \u2551 ") + chalk13.dim(`Tunnel: ${health.tunnelUrl}`.padEnd(55)) + chalk13.bold("\u2551"));
|
|
2083
|
+
}
|
|
2084
|
+
console.log(chalk13.bold(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
|
|
2085
|
+
console.log(chalk13.bold(" \u2551 ") + chalk13.dim("Type naturally to chat \xB7 /help for commands".padEnd(55)) + chalk13.bold("\u2551"));
|
|
2086
|
+
console.log(chalk13.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
|
+
console.log("");
|
|
2088
|
+
}
|
|
2089
|
+
async function getAgentList(sshPort) {
|
|
2090
|
+
try {
|
|
2091
|
+
const { stdout, code } = await sshExec(sshPort, "curl -sf http://localhost:4200/agents");
|
|
2092
|
+
if (code !== 0) return "Could not retrieve agent list";
|
|
2093
|
+
const agents = JSON.parse(stdout);
|
|
2094
|
+
if (!Array.isArray(agents)) return stdout;
|
|
2095
|
+
const lines = [""];
|
|
2096
|
+
lines.push(chalk13.bold(" Registered Agents:"));
|
|
2097
|
+
lines.push(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"));
|
|
2098
|
+
for (const agent of agents) {
|
|
2099
|
+
const name = agent.name ?? agent.id ?? "unknown";
|
|
2100
|
+
const role = agent.role ?? "";
|
|
2101
|
+
const status = agent.status === "active" ? chalk13.green("\u25CF") : chalk13.dim("\u25CB");
|
|
2102
|
+
lines.push(` ${status} ${chalk13.bold(name.padEnd(24))} ${chalk13.dim(role)}`);
|
|
2103
|
+
}
|
|
2104
|
+
lines.push("");
|
|
2105
|
+
return lines.join("\n");
|
|
2106
|
+
} catch {
|
|
2107
|
+
return " Could not retrieve agent list";
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
async function getStatus(sshPort) {
|
|
2111
|
+
try {
|
|
2112
|
+
const vmRunning = isVmRunning();
|
|
2113
|
+
const health = await checkHealth(sshPort, vmRunning);
|
|
2114
|
+
const check = chalk13.green("\u2713");
|
|
2115
|
+
const cross = chalk13.red("\u2717");
|
|
2116
|
+
const lines = [""];
|
|
2117
|
+
lines.push(chalk13.bold(" System Status:"));
|
|
2118
|
+
lines.push(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"));
|
|
2119
|
+
lines.push(` ${health.vmRunning ? check : cross} Virtual Machine`);
|
|
2120
|
+
lines.push(` ${health.sshReady ? check : cross} SSH Connection`);
|
|
2121
|
+
lines.push(` ${health.dockerReady ? check : cross} Docker Engine`);
|
|
2122
|
+
lines.push(` ${health.serverHealthy ? check : cross} NEXUS Engine`);
|
|
2123
|
+
if (health.tunnelUrl) {
|
|
2124
|
+
lines.push(` ${check} Tunnel: ${health.tunnelUrl}`);
|
|
2125
|
+
} else {
|
|
2126
|
+
lines.push(` ${cross} Tunnel: not active`);
|
|
2127
|
+
}
|
|
2128
|
+
lines.push("");
|
|
2129
|
+
return lines.join("\n");
|
|
2130
|
+
} catch {
|
|
2131
|
+
return " Could not check status";
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
async function getCost(sshPort) {
|
|
2135
|
+
try {
|
|
2136
|
+
const { stdout, code } = await sshExec(sshPort, "curl -sf http://localhost:4200/cost");
|
|
2137
|
+
if (code !== 0) return " Could not retrieve cost data";
|
|
2138
|
+
const data = JSON.parse(stdout);
|
|
2139
|
+
const lines = [""];
|
|
2140
|
+
lines.push(chalk13.bold(" Token Costs:"));
|
|
2141
|
+
lines.push(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"));
|
|
2142
|
+
if (data.total !== void 0) {
|
|
2143
|
+
lines.push(` Total: ${chalk13.bold.green("$" + Number(data.total).toFixed(4))}`);
|
|
2144
|
+
}
|
|
2145
|
+
if (data.today !== void 0) {
|
|
2146
|
+
lines.push(` Today: ${chalk13.bold("$" + Number(data.today).toFixed(4))}`);
|
|
2147
|
+
}
|
|
2148
|
+
if (data.by_agent && typeof data.by_agent === "object") {
|
|
2149
|
+
lines.push("");
|
|
2150
|
+
lines.push(chalk13.dim(" By Agent:"));
|
|
2151
|
+
for (const [agent, cost] of Object.entries(data.by_agent)) {
|
|
2152
|
+
lines.push(` ${agent.padEnd(20)} $${Number(cost).toFixed(4)}`);
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
lines.push("");
|
|
2156
|
+
return lines.join("\n");
|
|
2157
|
+
} catch {
|
|
2158
|
+
return " Could not retrieve cost data";
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
var shellCommand2 = new Command12("shell").description("Launch the interactive NEXUS shell").action(async () => {
|
|
2162
|
+
try {
|
|
2163
|
+
const config = loadConfig();
|
|
2164
|
+
if (!config) {
|
|
2165
|
+
log.error("No NEXUS configuration found. Run: buildwithnexus init");
|
|
2166
|
+
process.exit(1);
|
|
2167
|
+
}
|
|
2168
|
+
if (!isVmRunning()) {
|
|
2169
|
+
log.error("VM is not running. Start it with: buildwithnexus start");
|
|
2170
|
+
process.exit(1);
|
|
2171
|
+
}
|
|
2172
|
+
const spinner = createSpinner("Connecting to NEXUS...");
|
|
2173
|
+
spinner.start();
|
|
2174
|
+
const health = await checkHealth(config.sshPort, true);
|
|
2175
|
+
if (!health.serverHealthy) {
|
|
2176
|
+
fail(spinner, "NEXUS engine is not responding");
|
|
2177
|
+
log.warn("Check status: buildwithnexus status");
|
|
2178
|
+
process.exit(1);
|
|
2179
|
+
}
|
|
2180
|
+
succeed(spinner, "Connected to NEXUS engine");
|
|
2181
|
+
showShellBanner(health);
|
|
2182
|
+
const eventStream = new EventStream((event) => {
|
|
2183
|
+
const formatted = formatEvent(event);
|
|
2184
|
+
if (formatted) repl.write(formatted);
|
|
2185
|
+
});
|
|
2186
|
+
eventStream.start();
|
|
2187
|
+
const repl = new Repl(async (text) => {
|
|
2188
|
+
const thinkingSpinner = createSpinner("Processing...");
|
|
2189
|
+
thinkingSpinner.start();
|
|
2190
|
+
try {
|
|
2191
|
+
const response = await sendMessage2(config.sshPort, text);
|
|
2192
|
+
thinkingSpinner.stop();
|
|
2193
|
+
thinkingSpinner.clear();
|
|
2194
|
+
console.log("");
|
|
2195
|
+
console.log(chalk13.bold.cyan(" Chief of Staff:"));
|
|
2196
|
+
const lines = redact(response).split("\n");
|
|
2197
|
+
for (const line of lines) {
|
|
2198
|
+
console.log(chalk13.white(" " + line));
|
|
2199
|
+
}
|
|
2200
|
+
console.log("");
|
|
2201
|
+
} catch (err) {
|
|
2202
|
+
thinkingSpinner.stop();
|
|
2203
|
+
thinkingSpinner.clear();
|
|
2204
|
+
throw err;
|
|
2205
|
+
}
|
|
2206
|
+
});
|
|
2207
|
+
repl.registerCommand({
|
|
2208
|
+
name: "brainstorm",
|
|
2209
|
+
description: "Enter brainstorm mode",
|
|
2210
|
+
handler: async () => {
|
|
2211
|
+
console.log(chalk13.dim(" Entering brainstorm mode... (type 'exit' to return)"));
|
|
2212
|
+
const { input: input4 } = await import("@inquirer/prompts");
|
|
2213
|
+
const idea = await input4({ message: "What would you like to brainstorm?" });
|
|
2214
|
+
if (!idea.trim()) return;
|
|
2215
|
+
const brainstormSpinner = createSpinner("Chief of Staff is consulting the team...");
|
|
2216
|
+
brainstormSpinner.start();
|
|
2217
|
+
const response = await sendMessage2(config.sshPort, `[BRAINSTORM] The CEO wants to brainstorm: ${idea}`);
|
|
2218
|
+
brainstormSpinner.stop();
|
|
2219
|
+
brainstormSpinner.clear();
|
|
2220
|
+
console.log("");
|
|
2221
|
+
console.log(chalk13.bold.cyan(" Chief of Staff:"));
|
|
2222
|
+
for (const line of redact(response).split("\n")) {
|
|
2223
|
+
console.log(chalk13.white(" " + line));
|
|
2224
|
+
}
|
|
2225
|
+
console.log("");
|
|
2226
|
+
}
|
|
2227
|
+
});
|
|
2228
|
+
repl.registerCommand({
|
|
2229
|
+
name: "status",
|
|
2230
|
+
description: "Show system health status",
|
|
2231
|
+
handler: async () => {
|
|
2232
|
+
const result = await getStatus(config.sshPort);
|
|
2233
|
+
console.log(result);
|
|
2234
|
+
}
|
|
2235
|
+
});
|
|
2236
|
+
repl.registerCommand({
|
|
2237
|
+
name: "agents",
|
|
2238
|
+
description: "List registered agents",
|
|
2239
|
+
handler: async () => {
|
|
2240
|
+
const result = await getAgentList(config.sshPort);
|
|
2241
|
+
console.log(result);
|
|
2242
|
+
}
|
|
2243
|
+
});
|
|
2244
|
+
repl.registerCommand({
|
|
2245
|
+
name: "cost",
|
|
2246
|
+
description: "Show token usage and costs",
|
|
2247
|
+
handler: async () => {
|
|
2248
|
+
const result = await getCost(config.sshPort);
|
|
2249
|
+
console.log(result);
|
|
2250
|
+
}
|
|
2251
|
+
});
|
|
2252
|
+
repl.registerCommand({
|
|
2253
|
+
name: "logs",
|
|
2254
|
+
description: "Show recent server logs",
|
|
2255
|
+
handler: async () => {
|
|
2256
|
+
const { stdout } = await sshExec(config.sshPort, "tail -30 /home/nexus/.nexus/logs/server.log 2>/dev/null");
|
|
2257
|
+
console.log("");
|
|
2258
|
+
console.log(chalk13.bold(" Recent Logs:"));
|
|
2259
|
+
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"));
|
|
2260
|
+
for (const line of redact(stdout).split("\n")) {
|
|
2261
|
+
console.log(chalk13.dim(" " + line));
|
|
2262
|
+
}
|
|
2263
|
+
console.log("");
|
|
2264
|
+
}
|
|
2265
|
+
});
|
|
2266
|
+
repl.registerCommand({
|
|
2267
|
+
name: "ssh",
|
|
2268
|
+
description: "Open interactive SSH session",
|
|
2269
|
+
handler: async () => {
|
|
2270
|
+
const { openInteractiveSsh: openInteractiveSsh2 } = await Promise.resolve().then(() => (init_ssh(), ssh_exports));
|
|
2271
|
+
eventStream.stop();
|
|
2272
|
+
await openInteractiveSsh2(config.sshPort);
|
|
2273
|
+
eventStream.start();
|
|
2274
|
+
}
|
|
2275
|
+
});
|
|
2276
|
+
process.on("SIGINT", () => {
|
|
2277
|
+
eventStream.stop();
|
|
2278
|
+
repl.stop();
|
|
2279
|
+
});
|
|
2280
|
+
await repl.start();
|
|
2281
|
+
} catch (err) {
|
|
2282
|
+
if (err.code === "ERR_USE_AFTER_CLOSE") {
|
|
2283
|
+
console.log("");
|
|
2284
|
+
log.success("Shell session ended");
|
|
2285
|
+
return;
|
|
2286
|
+
}
|
|
2287
|
+
const safeErr = redactError(err);
|
|
2288
|
+
log.error(`Shell failed: ${safeErr.message}`);
|
|
2289
|
+
process.exit(1);
|
|
2290
|
+
}
|
|
2291
|
+
});
|
|
2292
|
+
|
|
1702
2293
|
// src/cli.ts
|
|
1703
|
-
var cli = new
|
|
2294
|
+
var cli = new Command13().name("buildwithnexus").description("Auto-scaffold and launch a fully autonomous NEXUS runtime").version("0.2.8");
|
|
1704
2295
|
cli.addCommand(initCommand);
|
|
1705
2296
|
cli.addCommand(startCommand);
|
|
1706
2297
|
cli.addCommand(stopCommand);
|
|
@@ -1712,23 +2303,34 @@ cli.addCommand(destroyCommand);
|
|
|
1712
2303
|
cli.addCommand(keysCommand);
|
|
1713
2304
|
cli.addCommand(sshCommand);
|
|
1714
2305
|
cli.addCommand(brainstormCommand);
|
|
1715
|
-
cli.
|
|
2306
|
+
cli.addCommand(shellCommand2);
|
|
2307
|
+
cli.action(async () => {
|
|
2308
|
+
try {
|
|
2309
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_secrets(), secrets_exports));
|
|
2310
|
+
const { isVmRunning: isVmRunning3 } = await Promise.resolve().then(() => (init_qemu(), qemu_exports));
|
|
2311
|
+
const config = loadConfig2();
|
|
2312
|
+
if (config && isVmRunning3()) {
|
|
2313
|
+
await shellCommand2.parseAsync([], { from: "user" });
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
2316
|
+
} catch {
|
|
2317
|
+
}
|
|
1716
2318
|
cli.help();
|
|
1717
2319
|
});
|
|
1718
2320
|
|
|
1719
2321
|
// src/core/update-notifier.ts
|
|
1720
|
-
import
|
|
1721
|
-
import
|
|
2322
|
+
import fs11 from "fs";
|
|
2323
|
+
import path12 from "path";
|
|
1722
2324
|
import os3 from "os";
|
|
1723
2325
|
import https from "https";
|
|
1724
|
-
import
|
|
2326
|
+
import chalk14 from "chalk";
|
|
1725
2327
|
var PACKAGE_NAME = "buildwithnexus";
|
|
1726
2328
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
1727
|
-
var STATE_DIR =
|
|
1728
|
-
var STATE_FILE =
|
|
2329
|
+
var STATE_DIR = path12.join(os3.homedir(), ".buildwithnexus");
|
|
2330
|
+
var STATE_FILE = path12.join(STATE_DIR, ".update-check.json");
|
|
1729
2331
|
function readState() {
|
|
1730
2332
|
try {
|
|
1731
|
-
const raw =
|
|
2333
|
+
const raw = fs11.readFileSync(STATE_FILE, "utf-8");
|
|
1732
2334
|
return JSON.parse(raw);
|
|
1733
2335
|
} catch {
|
|
1734
2336
|
return { lastCheck: 0, latestVersion: null };
|
|
@@ -1736,8 +2338,8 @@ function readState() {
|
|
|
1736
2338
|
}
|
|
1737
2339
|
function writeState(state) {
|
|
1738
2340
|
try {
|
|
1739
|
-
|
|
1740
|
-
|
|
2341
|
+
fs11.mkdirSync(STATE_DIR, { recursive: true, mode: 448 });
|
|
2342
|
+
fs11.writeFileSync(STATE_FILE, JSON.stringify(state), { mode: 384 });
|
|
1741
2343
|
} catch {
|
|
1742
2344
|
}
|
|
1743
2345
|
}
|
|
@@ -1802,8 +2404,8 @@ async function checkForUpdates(currentVersion) {
|
|
|
1802
2404
|
function printUpdateBanner(current, latest) {
|
|
1803
2405
|
const msg = [
|
|
1804
2406
|
"",
|
|
1805
|
-
|
|
1806
|
-
|
|
2407
|
+
chalk14.yellow(` Update available: ${current} \u2192 ${latest}`),
|
|
2408
|
+
chalk14.cyan(` Run: npm update -g buildwithnexus`),
|
|
1807
2409
|
""
|
|
1808
2410
|
].join("\n");
|
|
1809
2411
|
process.stderr.write(msg + "\n");
|