buildwithnexus 0.2.7 → 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 +924 -315
- 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.7 \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: "-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() });
|
|
@@ -593,31 +414,38 @@ async function resolvePortConflicts(ports) {
|
|
|
593
414
|
async function launchVm(platform, diskPath, initIsoPath, ram, cpus, ports) {
|
|
594
415
|
const machineArg = platform.os === "mac" ? "-machine virt,gic-version=3" : "-machine pc";
|
|
595
416
|
const biosArgs = fs3.existsSync(platform.biosPath) ? ["-bios", platform.biosPath] : [];
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
417
|
+
function buildArgs(cpuFlag) {
|
|
418
|
+
return [
|
|
419
|
+
...machineArg.split(" "),
|
|
420
|
+
...cpuFlag.split(" "),
|
|
421
|
+
"-m",
|
|
422
|
+
`${ram}G`,
|
|
423
|
+
"-smp",
|
|
424
|
+
`${cpus}`,
|
|
425
|
+
"-drive",
|
|
426
|
+
`file=${diskPath},if=virtio,cache=writethrough`,
|
|
427
|
+
"-drive",
|
|
428
|
+
`file=${initIsoPath},if=virtio,format=raw,cache=writethrough`,
|
|
429
|
+
"-display",
|
|
430
|
+
"none",
|
|
431
|
+
"-serial",
|
|
432
|
+
"none",
|
|
433
|
+
"-net",
|
|
434
|
+
"nic,model=virtio",
|
|
435
|
+
"-net",
|
|
436
|
+
`user,hostfwd=tcp::${ports.ssh}-:22,hostfwd=tcp::${ports.http}-:4200,hostfwd=tcp::${ports.https}-:443`,
|
|
437
|
+
...biosArgs,
|
|
438
|
+
"-pidfile",
|
|
439
|
+
PID_FILE,
|
|
440
|
+
"-daemonize"
|
|
441
|
+
];
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
await execa(platform.qemuBinary, buildArgs(platform.qemuCpuFlag), { env: scrubEnv() });
|
|
445
|
+
} catch {
|
|
446
|
+
const fallbackCpu = platform.os === "mac" ? "-cpu max" : "-cpu qemu64";
|
|
447
|
+
await execa(platform.qemuBinary, buildArgs(fallbackCpu), { env: scrubEnv() });
|
|
448
|
+
}
|
|
621
449
|
return ports;
|
|
622
450
|
}
|
|
623
451
|
function readValidPid() {
|
|
@@ -652,18 +480,36 @@ function stopVm() {
|
|
|
652
480
|
function getVmPid() {
|
|
653
481
|
return readValidPid();
|
|
654
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
|
+
});
|
|
655
495
|
|
|
656
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
|
+
});
|
|
657
508
|
import fs4 from "fs";
|
|
658
509
|
import path4 from "path";
|
|
659
510
|
import crypto3 from "crypto";
|
|
660
511
|
import { execa as execa2 } from "execa";
|
|
661
512
|
import { NodeSSH } from "node-ssh";
|
|
662
|
-
var SSH_DIR = path4.join(NEXUS_HOME2, "ssh");
|
|
663
|
-
var SSH_KEY = path4.join(SSH_DIR, "id_nexus_vm");
|
|
664
|
-
var SSH_PUB_KEY = path4.join(SSH_DIR, "id_nexus_vm.pub");
|
|
665
|
-
var KNOWN_HOSTS = path4.join(SSH_DIR, "known_hosts_nexus_vm");
|
|
666
|
-
var PINNED_HOST_KEY = path4.join(SSH_DIR, "vm_host_key.pin");
|
|
667
513
|
function getHostVerifier() {
|
|
668
514
|
if (!fs4.existsSync(PINNED_HOST_KEY)) {
|
|
669
515
|
return (key) => {
|
|
@@ -681,6 +527,9 @@ function getHostVerifier() {
|
|
|
681
527
|
return match;
|
|
682
528
|
};
|
|
683
529
|
}
|
|
530
|
+
function getKeyPath() {
|
|
531
|
+
return SSH_KEY;
|
|
532
|
+
}
|
|
684
533
|
function getPubKey() {
|
|
685
534
|
return fs4.readFileSync(SSH_PUB_KEY, "utf-8").trim();
|
|
686
535
|
}
|
|
@@ -769,14 +618,256 @@ async function sshUploadFile(port, localPath, remotePath) {
|
|
|
769
618
|
privateKeyPath: SSH_KEY,
|
|
770
619
|
hostVerifier: getHostVerifier()
|
|
771
620
|
});
|
|
772
|
-
await ssh.putFile(localPath, remotePath);
|
|
773
|
-
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
|
+
};
|
|
774
823
|
}
|
|
775
|
-
|
|
776
|
-
|
|
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}`);
|
|
777
861
|
}
|
|
778
862
|
|
|
863
|
+
// src/commands/init.ts
|
|
864
|
+
init_secrets();
|
|
865
|
+
init_qemu();
|
|
866
|
+
init_ssh();
|
|
867
|
+
|
|
779
868
|
// src/core/cloudinit.ts
|
|
869
|
+
init_secrets();
|
|
870
|
+
init_dlp();
|
|
780
871
|
import fs5 from "fs";
|
|
781
872
|
import path5 from "path";
|
|
782
873
|
import ejs from "ejs";
|
|
@@ -870,6 +961,7 @@ async function createCloudInitIso(userDataPath) {
|
|
|
870
961
|
}
|
|
871
962
|
|
|
872
963
|
// src/core/health.ts
|
|
964
|
+
init_ssh();
|
|
873
965
|
async function checkHealth(port, vmRunning) {
|
|
874
966
|
const status = {
|
|
875
967
|
vmRunning,
|
|
@@ -930,6 +1022,8 @@ async function waitForCloudInit(port, timeoutMs = 9e5) {
|
|
|
930
1022
|
}
|
|
931
1023
|
|
|
932
1024
|
// src/core/tunnel.ts
|
|
1025
|
+
init_ssh();
|
|
1026
|
+
init_dlp();
|
|
933
1027
|
var CLOUDFLARED_VERSION = "2024.12.2";
|
|
934
1028
|
var CLOUDFLARED_SHA256 = {
|
|
935
1029
|
amd64: "40ec9a0f5b58e3b04183aaf01c4ddd4dbc6af39b0f06be4b7ce8b1011d0a07ab",
|
|
@@ -977,6 +1071,8 @@ async function stopTunnel(sshPort) {
|
|
|
977
1071
|
}
|
|
978
1072
|
|
|
979
1073
|
// src/commands/init.ts
|
|
1074
|
+
init_dlp();
|
|
1075
|
+
init_ssh();
|
|
980
1076
|
import fs6 from "fs";
|
|
981
1077
|
import path6 from "path";
|
|
982
1078
|
import os2 from "os";
|
|
@@ -1098,15 +1194,14 @@ var initCommand = new Command("init").description("Scaffold and launch a new NEX
|
|
|
1098
1194
|
spinner.start();
|
|
1099
1195
|
await sshUploadFile(config.sshPort, tarballPath, "/tmp/nexus-release.tar.gz");
|
|
1100
1196
|
succeed(spinner, "Tarball uploaded");
|
|
1101
|
-
spinner = createSpinner("
|
|
1197
|
+
spinner = createSpinner("Staging API keys...");
|
|
1102
1198
|
spinner.start();
|
|
1103
1199
|
const keysContent = Object.entries(keys).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
|
|
1104
1200
|
const tmpKeysPath = path6.join(os2.tmpdir(), `.nexus-keys-${crypto4.randomBytes(8).toString("hex")}`);
|
|
1105
1201
|
fs6.writeFileSync(tmpKeysPath, keysContent, { mode: 384 });
|
|
1106
1202
|
try {
|
|
1107
|
-
await
|
|
1108
|
-
await
|
|
1109
|
-
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");
|
|
1110
1205
|
} finally {
|
|
1111
1206
|
try {
|
|
1112
1207
|
fs6.writeFileSync(tmpKeysPath, "0".repeat(keysContent.length));
|
|
@@ -1114,7 +1209,7 @@ var initCommand = new Command("init").description("Scaffold and launch a new NEX
|
|
|
1114
1209
|
} catch {
|
|
1115
1210
|
}
|
|
1116
1211
|
}
|
|
1117
|
-
succeed(spinner, "API keys
|
|
1212
|
+
succeed(spinner, "API keys staged");
|
|
1118
1213
|
spinner = createSpinner("Cloud-init provisioning (extracting NEXUS, building Docker, installing deps)...");
|
|
1119
1214
|
spinner.start();
|
|
1120
1215
|
const cloudInitDone = await waitForCloudInit(config.sshPort);
|
|
@@ -1124,6 +1219,10 @@ var initCommand = new Command("init").description("Scaffold and launch a new NEX
|
|
|
1124
1219
|
process.exit(1);
|
|
1125
1220
|
}
|
|
1126
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");
|
|
1127
1226
|
showPhase(8, TOTAL_PHASES, "NEXUS Server Startup");
|
|
1128
1227
|
spinner = createSpinner("Waiting for NEXUS server...");
|
|
1129
1228
|
spinner.start();
|
|
@@ -1166,6 +1265,10 @@ var initCommand = new Command("init").description("Scaffold and launch a new NEX
|
|
|
1166
1265
|
|
|
1167
1266
|
// src/commands/start.ts
|
|
1168
1267
|
import { Command as Command2 } from "commander";
|
|
1268
|
+
init_secrets();
|
|
1269
|
+
init_qemu();
|
|
1270
|
+
init_ssh();
|
|
1271
|
+
init_secrets();
|
|
1169
1272
|
import path7 from "path";
|
|
1170
1273
|
var startCommand = new Command2("start").description("Start the NEXUS runtime").action(async () => {
|
|
1171
1274
|
const config = loadConfig();
|
|
@@ -1220,6 +1323,9 @@ var startCommand = new Command2("start").description("Start the NEXUS runtime").
|
|
|
1220
1323
|
|
|
1221
1324
|
// src/commands/stop.ts
|
|
1222
1325
|
import { Command as Command3 } from "commander";
|
|
1326
|
+
init_secrets();
|
|
1327
|
+
init_qemu();
|
|
1328
|
+
init_ssh();
|
|
1223
1329
|
var stopCommand = new Command3("stop").description("Gracefully shut down the NEXUS runtime").action(async () => {
|
|
1224
1330
|
const config = loadConfig();
|
|
1225
1331
|
if (!config) {
|
|
@@ -1256,6 +1362,8 @@ var stopCommand = new Command3("stop").description("Gracefully shut down the NEX
|
|
|
1256
1362
|
// src/commands/status.ts
|
|
1257
1363
|
import { Command as Command4 } from "commander";
|
|
1258
1364
|
import chalk6 from "chalk";
|
|
1365
|
+
init_secrets();
|
|
1366
|
+
init_qemu();
|
|
1259
1367
|
var statusCommand = new Command4("status").description("Check NEXUS runtime health").option("--json", "Output as JSON").action(async (opts) => {
|
|
1260
1368
|
const config = loadConfig();
|
|
1261
1369
|
if (!config) {
|
|
@@ -1287,6 +1395,8 @@ var statusCommand = new Command4("status").description("Check NEXUS runtime heal
|
|
|
1287
1395
|
import { Command as Command5 } from "commander";
|
|
1288
1396
|
import chalk7 from "chalk";
|
|
1289
1397
|
import fs7 from "fs";
|
|
1398
|
+
init_qemu();
|
|
1399
|
+
init_secrets();
|
|
1290
1400
|
import path8 from "path";
|
|
1291
1401
|
import { execa as execa4 } from "execa";
|
|
1292
1402
|
var doctorCommand = new Command5("doctor").description("Diagnose NEXUS runtime environment").action(async () => {
|
|
@@ -1354,6 +1464,10 @@ var doctorCommand = new Command5("doctor").description("Diagnose NEXUS runtime e
|
|
|
1354
1464
|
|
|
1355
1465
|
// src/commands/logs.ts
|
|
1356
1466
|
import { Command as Command6 } from "commander";
|
|
1467
|
+
init_secrets();
|
|
1468
|
+
init_qemu();
|
|
1469
|
+
init_ssh();
|
|
1470
|
+
init_dlp();
|
|
1357
1471
|
import { execa as execa5 } from "execa";
|
|
1358
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) => {
|
|
1359
1473
|
const config = loadConfig();
|
|
@@ -1392,6 +1506,9 @@ import { Command as Command7 } from "commander";
|
|
|
1392
1506
|
import path9 from "path";
|
|
1393
1507
|
import fs8 from "fs";
|
|
1394
1508
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1509
|
+
init_secrets();
|
|
1510
|
+
init_qemu();
|
|
1511
|
+
init_ssh();
|
|
1395
1512
|
function getReleaseTarball2() {
|
|
1396
1513
|
const dir = path9.dirname(fileURLToPath2(import.meta.url));
|
|
1397
1514
|
const tarballPath = path9.join(dir, "nexus-release.tar.gz");
|
|
@@ -1450,6 +1567,8 @@ import { Command as Command8 } from "commander";
|
|
|
1450
1567
|
import chalk8 from "chalk";
|
|
1451
1568
|
import fs9 from "fs";
|
|
1452
1569
|
import { input as input2 } from "@inquirer/prompts";
|
|
1570
|
+
init_secrets();
|
|
1571
|
+
init_qemu();
|
|
1453
1572
|
import path10 from "path";
|
|
1454
1573
|
var destroyCommand = new Command8("destroy").description("Remove NEXUS VM and all data").option("--force", "Skip confirmation").action(async (opts) => {
|
|
1455
1574
|
const config = loadConfig();
|
|
@@ -1504,6 +1623,8 @@ var destroyCommand = new Command8("destroy").description("Remove NEXUS VM and al
|
|
|
1504
1623
|
import { Command as Command9 } from "commander";
|
|
1505
1624
|
import { password as password2 } from "@inquirer/prompts";
|
|
1506
1625
|
import chalk9 from "chalk";
|
|
1626
|
+
init_secrets();
|
|
1627
|
+
init_dlp();
|
|
1507
1628
|
var keysCommand = new Command9("keys").description("Manage API keys");
|
|
1508
1629
|
keysCommand.command("list").description("Show configured API keys (masked)").action(() => {
|
|
1509
1630
|
const keys = loadKeys();
|
|
@@ -1560,6 +1681,9 @@ keysCommand.command("set <key>").description("Set or update an API key (e.g. ANT
|
|
|
1560
1681
|
|
|
1561
1682
|
// src/commands/ssh.ts
|
|
1562
1683
|
import { Command as Command10 } from "commander";
|
|
1684
|
+
init_secrets();
|
|
1685
|
+
init_qemu();
|
|
1686
|
+
init_ssh();
|
|
1563
1687
|
var sshCommand = new Command10("ssh").description("Open an SSH session into the NEXUS VM").action(async () => {
|
|
1564
1688
|
const config = loadConfig();
|
|
1565
1689
|
if (!config) {
|
|
@@ -1578,6 +1702,10 @@ var sshCommand = new Command10("ssh").description("Open an SSH session into the
|
|
|
1578
1702
|
import { Command as Command11 } from "commander";
|
|
1579
1703
|
import chalk10 from "chalk";
|
|
1580
1704
|
import { input as input3 } from "@inquirer/prompts";
|
|
1705
|
+
init_secrets();
|
|
1706
|
+
init_qemu();
|
|
1707
|
+
init_ssh();
|
|
1708
|
+
init_dlp();
|
|
1581
1709
|
var COS_PREFIX = chalk10.bold.cyan(" Chief of Staff");
|
|
1582
1710
|
var YOU_PREFIX = chalk10.bold.white(" You");
|
|
1583
1711
|
var DIVIDER = chalk10.dim(" " + "\u2500".repeat(56));
|
|
@@ -1692,8 +1820,478 @@ var brainstormCommand = new Command11("brainstorm").description("Brainstorm an i
|
|
|
1692
1820
|
}
|
|
1693
1821
|
});
|
|
1694
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
|
+
|
|
1695
2293
|
// src/cli.ts
|
|
1696
|
-
var cli = new
|
|
2294
|
+
var cli = new Command13().name("buildwithnexus").description("Auto-scaffold and launch a fully autonomous NEXUS runtime").version("0.2.8");
|
|
1697
2295
|
cli.addCommand(initCommand);
|
|
1698
2296
|
cli.addCommand(startCommand);
|
|
1699
2297
|
cli.addCommand(stopCommand);
|
|
@@ -1705,23 +2303,34 @@ cli.addCommand(destroyCommand);
|
|
|
1705
2303
|
cli.addCommand(keysCommand);
|
|
1706
2304
|
cli.addCommand(sshCommand);
|
|
1707
2305
|
cli.addCommand(brainstormCommand);
|
|
1708
|
-
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
|
+
}
|
|
1709
2318
|
cli.help();
|
|
1710
2319
|
});
|
|
1711
2320
|
|
|
1712
2321
|
// src/core/update-notifier.ts
|
|
1713
|
-
import
|
|
1714
|
-
import
|
|
2322
|
+
import fs11 from "fs";
|
|
2323
|
+
import path12 from "path";
|
|
1715
2324
|
import os3 from "os";
|
|
1716
2325
|
import https from "https";
|
|
1717
|
-
import
|
|
2326
|
+
import chalk14 from "chalk";
|
|
1718
2327
|
var PACKAGE_NAME = "buildwithnexus";
|
|
1719
2328
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
1720
|
-
var STATE_DIR =
|
|
1721
|
-
var STATE_FILE =
|
|
2329
|
+
var STATE_DIR = path12.join(os3.homedir(), ".buildwithnexus");
|
|
2330
|
+
var STATE_FILE = path12.join(STATE_DIR, ".update-check.json");
|
|
1722
2331
|
function readState() {
|
|
1723
2332
|
try {
|
|
1724
|
-
const raw =
|
|
2333
|
+
const raw = fs11.readFileSync(STATE_FILE, "utf-8");
|
|
1725
2334
|
return JSON.parse(raw);
|
|
1726
2335
|
} catch {
|
|
1727
2336
|
return { lastCheck: 0, latestVersion: null };
|
|
@@ -1729,8 +2338,8 @@ function readState() {
|
|
|
1729
2338
|
}
|
|
1730
2339
|
function writeState(state) {
|
|
1731
2340
|
try {
|
|
1732
|
-
|
|
1733
|
-
|
|
2341
|
+
fs11.mkdirSync(STATE_DIR, { recursive: true, mode: 448 });
|
|
2342
|
+
fs11.writeFileSync(STATE_FILE, JSON.stringify(state), { mode: 384 });
|
|
1734
2343
|
} catch {
|
|
1735
2344
|
}
|
|
1736
2345
|
}
|
|
@@ -1795,8 +2404,8 @@ async function checkForUpdates(currentVersion) {
|
|
|
1795
2404
|
function printUpdateBanner(current, latest) {
|
|
1796
2405
|
const msg = [
|
|
1797
2406
|
"",
|
|
1798
|
-
|
|
1799
|
-
|
|
2407
|
+
chalk14.yellow(` Update available: ${current} \u2192 ${latest}`),
|
|
2408
|
+
chalk14.cyan(` Run: npm update -g buildwithnexus`),
|
|
1800
2409
|
""
|
|
1801
2410
|
].join("\n");
|
|
1802
2411
|
process.stderr.write(msg + "\n");
|