buildwithnexus 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +2017 -1793
- package/dist/deep-agents-bin.js +42 -13
- package/package.json +2 -1
package/dist/bin.js
CHANGED
|
@@ -9,27 +9,215 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// src/core/
|
|
13
|
-
import
|
|
12
|
+
// src/core/dlp.ts
|
|
13
|
+
import crypto from "crypto";
|
|
14
|
+
import fs from "fs";
|
|
14
15
|
import path from "path";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
google: process.env.GOOGLE_API_KEY || void 0
|
|
21
|
-
};
|
|
16
|
+
function shellEscape(value) {
|
|
17
|
+
if (value.includes("\0")) {
|
|
18
|
+
throw new DlpViolation("Null byte in shell argument");
|
|
19
|
+
}
|
|
20
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
22
21
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
22
|
+
function shellCommand(strings, ...values) {
|
|
23
|
+
let result = strings[0];
|
|
24
|
+
for (let i = 0; i < values.length; i++) {
|
|
25
|
+
result += shellEscape(values[i]) + strings[i + 1];
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
function redact(text) {
|
|
30
|
+
let result = text;
|
|
31
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
32
|
+
pattern.lastIndex = 0;
|
|
33
|
+
result = result.replace(pattern, "[REDACTED]");
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
function redactError(err) {
|
|
38
|
+
if (err instanceof Error) {
|
|
39
|
+
const safe = new Error(redact(err.message));
|
|
40
|
+
safe.name = err.name;
|
|
41
|
+
if (err.stack) safe.stack = redact(err.stack);
|
|
42
|
+
return safe;
|
|
43
|
+
}
|
|
44
|
+
return new Error(redact(String(err)));
|
|
45
|
+
}
|
|
46
|
+
function validateKeyValue(keyName, value) {
|
|
47
|
+
if (FORBIDDEN_KEY_CHARS.test(value)) {
|
|
48
|
+
throw new DlpViolation(
|
|
49
|
+
`${keyName} contains characters that are not permitted in API keys`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (value.length < 10 || value.length > 256) {
|
|
53
|
+
throw new DlpViolation(
|
|
54
|
+
`${keyName} length out of expected range (10-256 characters)`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
const validator = KEY_VALIDATORS[keyName];
|
|
58
|
+
if (validator && !validator.test(value)) {
|
|
59
|
+
throw new DlpViolation(
|
|
60
|
+
`${keyName} does not match the expected format for this key type`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function validateAllKeys(keys) {
|
|
65
|
+
const violations = [];
|
|
66
|
+
for (const [name, value] of Object.entries(keys)) {
|
|
67
|
+
if (!value) continue;
|
|
68
|
+
try {
|
|
69
|
+
validateKeyValue(name, value);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
if (err instanceof DlpViolation) violations.push(err.message);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return violations;
|
|
75
|
+
}
|
|
76
|
+
function computeFileHmac(filePath, secret) {
|
|
77
|
+
const content = fs.readFileSync(filePath);
|
|
78
|
+
return crypto.createHmac("sha256", secret).update(content).digest("hex");
|
|
79
|
+
}
|
|
80
|
+
function sealKeysFile(keysPath, masterSecret) {
|
|
81
|
+
const hmac = computeFileHmac(keysPath, masterSecret);
|
|
82
|
+
fs.writeFileSync(HMAC_PATH, hmac, { mode: 384 });
|
|
83
|
+
}
|
|
84
|
+
function verifyKeysFile(keysPath, masterSecret) {
|
|
85
|
+
const keysExist = fs.existsSync(keysPath);
|
|
86
|
+
const hmacExist = fs.existsSync(HMAC_PATH);
|
|
87
|
+
if (!keysExist && !hmacExist) return true;
|
|
88
|
+
if (keysExist && !hmacExist) return false;
|
|
89
|
+
try {
|
|
90
|
+
const stored = fs.readFileSync(HMAC_PATH, "utf-8").trim();
|
|
91
|
+
const computed = computeFileHmac(keysPath, masterSecret);
|
|
92
|
+
return crypto.timingSafeEqual(
|
|
93
|
+
Buffer.from(stored, "hex"),
|
|
94
|
+
Buffer.from(computed, "hex")
|
|
95
|
+
);
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function audit(event, detail = "") {
|
|
101
|
+
try {
|
|
102
|
+
const dir = path.dirname(AUDIT_PATH);
|
|
103
|
+
if (!fs.existsSync(dir)) return;
|
|
104
|
+
if (fs.existsSync(AUDIT_PATH)) {
|
|
105
|
+
const stat = fs.statSync(AUDIT_PATH);
|
|
106
|
+
if (stat.size > MAX_AUDIT_SIZE) {
|
|
107
|
+
const rotated = AUDIT_PATH + ".1";
|
|
108
|
+
if (fs.existsSync(rotated)) fs.unlinkSync(rotated);
|
|
109
|
+
fs.renameSync(AUDIT_PATH, rotated);
|
|
110
|
+
try {
|
|
111
|
+
fs.chmodSync(rotated, 384);
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const line = `${(/* @__PURE__ */ new Date()).toISOString()} | ${event} | ${redact(detail)}
|
|
117
|
+
`;
|
|
118
|
+
fs.appendFileSync(AUDIT_PATH, line, { mode: 384 });
|
|
119
|
+
try {
|
|
120
|
+
fs.chmodSync(AUDIT_PATH, 384);
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
var NEXUS_HOME, SECRET_PATTERNS, FORBIDDEN_KEY_CHARS, KEY_VALIDATORS, DlpViolation, HMAC_PATH, AUDIT_PATH, MAX_AUDIT_SIZE;
|
|
127
|
+
var init_dlp = __esm({
|
|
128
|
+
"src/core/dlp.ts"() {
|
|
129
|
+
"use strict";
|
|
130
|
+
NEXUS_HOME = path.join(process.env.HOME || "~", ".buildwithnexus");
|
|
131
|
+
SECRET_PATTERNS = [
|
|
132
|
+
/sk-ant-api03-[A-Za-z0-9_-]{20,}/g,
|
|
133
|
+
// Anthropic API key
|
|
134
|
+
/sk-[A-Za-z0-9]{20,}/g,
|
|
135
|
+
// OpenAI API key
|
|
136
|
+
/AIza[A-Za-z0-9_-]{35}/g
|
|
137
|
+
// Google AI API key
|
|
138
|
+
];
|
|
139
|
+
FORBIDDEN_KEY_CHARS = /[\n\r\t'"\\`${}();&|<>!#%^]/;
|
|
140
|
+
KEY_VALIDATORS = {
|
|
141
|
+
ANTHROPIC_API_KEY: /^sk-ant-[A-Za-z0-9_-]{20,}$/,
|
|
142
|
+
OPENAI_API_KEY: /^sk-[A-Za-z0-9_-]{20,}$/,
|
|
143
|
+
GOOGLE_API_KEY: /^AIza[A-Za-z0-9_-]{35,}$/,
|
|
144
|
+
NEXUS_MASTER_SECRET: /^[A-Za-z0-9_-]{20,64}$/
|
|
145
|
+
};
|
|
146
|
+
DlpViolation = class extends Error {
|
|
147
|
+
constructor(message) {
|
|
148
|
+
super(message);
|
|
149
|
+
this.name = "DlpViolation";
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
HMAC_PATH = path.join(NEXUS_HOME, ".keys.hmac");
|
|
153
|
+
AUDIT_PATH = path.join(NEXUS_HOME, "audit.log");
|
|
154
|
+
MAX_AUDIT_SIZE = 10 * 1024 * 1024;
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// src/core/secrets.ts
|
|
159
|
+
import fs2 from "fs";
|
|
160
|
+
import path2 from "path";
|
|
161
|
+
import crypto2 from "crypto";
|
|
162
|
+
function ensureHome() {
|
|
163
|
+
fs2.mkdirSync(NEXUS_HOME2, { recursive: true, mode: 448 });
|
|
164
|
+
fs2.mkdirSync(path2.join(NEXUS_HOME2, "vm", "images"), { recursive: true, mode: 448 });
|
|
165
|
+
fs2.mkdirSync(path2.join(NEXUS_HOME2, "vm", "configs"), { recursive: true, mode: 448 });
|
|
166
|
+
fs2.mkdirSync(path2.join(NEXUS_HOME2, "vm", "logs"), { recursive: true, mode: 448 });
|
|
167
|
+
fs2.mkdirSync(path2.join(NEXUS_HOME2, "ssh"), { recursive: true, mode: 448 });
|
|
168
|
+
}
|
|
169
|
+
function generateMasterSecret() {
|
|
170
|
+
return crypto2.randomBytes(32).toString("base64url");
|
|
171
|
+
}
|
|
172
|
+
function saveConfig(config) {
|
|
173
|
+
const { masterSecret: _secret, ...safeConfig } = config;
|
|
174
|
+
fs2.writeFileSync(CONFIG_PATH, JSON.stringify(safeConfig, null, 2), { mode: 384 });
|
|
175
|
+
}
|
|
176
|
+
function loadConfig() {
|
|
177
|
+
if (!fs2.existsSync(CONFIG_PATH)) return null;
|
|
178
|
+
return JSON.parse(fs2.readFileSync(CONFIG_PATH, "utf-8"));
|
|
179
|
+
}
|
|
180
|
+
function saveKeys(keys) {
|
|
181
|
+
const violations = validateAllKeys(keys);
|
|
182
|
+
if (violations.length > 0) {
|
|
183
|
+
throw new DlpViolation(`Key validation failed: ${violations.join("; ")}`);
|
|
184
|
+
}
|
|
185
|
+
const lines = Object.entries(keys).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`);
|
|
186
|
+
fs2.writeFileSync(KEYS_PATH, lines.join("\n") + "\n", { mode: 384 });
|
|
187
|
+
sealKeysFile(KEYS_PATH, keys.NEXUS_MASTER_SECRET);
|
|
188
|
+
audit("keys_saved", `${Object.keys(keys).filter((k) => keys[k]).length} keys saved`);
|
|
189
|
+
}
|
|
190
|
+
function loadKeys() {
|
|
191
|
+
if (!fs2.existsSync(KEYS_PATH)) return null;
|
|
192
|
+
const content = fs2.readFileSync(KEYS_PATH, "utf-8");
|
|
193
|
+
const keys = {};
|
|
194
|
+
for (const line of content.split("\n")) {
|
|
195
|
+
const eq = line.indexOf("=");
|
|
196
|
+
if (eq > 0) keys[line.slice(0, eq)] = line.slice(eq + 1);
|
|
197
|
+
}
|
|
198
|
+
const result = keys;
|
|
199
|
+
if (result.NEXUS_MASTER_SECRET && !verifyKeysFile(KEYS_PATH, result.NEXUS_MASTER_SECRET)) {
|
|
200
|
+
audit("keys_tampered", "HMAC mismatch on .env.keys");
|
|
201
|
+
throw new DlpViolation(
|
|
202
|
+
".env.keys has been modified outside of buildwithnexus. Run 'buildwithnexus keys set' to re-enter your keys, or 'buildwithnexus destroy' to start fresh."
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
audit("keys_loaded", `${Object.keys(keys).length} keys loaded`);
|
|
206
|
+
return result;
|
|
25
207
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
208
|
+
function maskKey(key) {
|
|
209
|
+
if (key.length <= 8) return "***";
|
|
210
|
+
const reveal = Math.min(4, Math.floor(key.length * 0.1));
|
|
211
|
+
return key.slice(0, reveal) + "..." + key.slice(-reveal);
|
|
29
212
|
}
|
|
30
|
-
var
|
|
31
|
-
|
|
213
|
+
var NEXUS_HOME2, CONFIG_PATH, KEYS_PATH;
|
|
214
|
+
var init_secrets = __esm({
|
|
215
|
+
"src/core/secrets.ts"() {
|
|
32
216
|
"use strict";
|
|
217
|
+
init_dlp();
|
|
218
|
+
NEXUS_HOME2 = process.env.NEXUS_HOME || path2.join(process.env.HOME || "~", ".buildwithnexus");
|
|
219
|
+
CONFIG_PATH = process.env.NEXUS_CONFIG_PATH || path2.join(NEXUS_HOME2, "config.json");
|
|
220
|
+
KEYS_PATH = process.env.NEXUS_KEYS_PATH || path2.join(NEXUS_HOME2, ".env.keys");
|
|
33
221
|
}
|
|
34
222
|
});
|
|
35
223
|
|
|
@@ -38,1682 +226,1823 @@ var init_command_exports = {};
|
|
|
38
226
|
__export(init_command_exports, {
|
|
39
227
|
deepAgentsInitCommand: () => deepAgentsInitCommand
|
|
40
228
|
});
|
|
41
|
-
import fs from "fs";
|
|
42
|
-
import path2 from "path";
|
|
43
|
-
import os2 from "os";
|
|
44
229
|
import * as readline from "readline";
|
|
45
230
|
async function deepAgentsInitCommand() {
|
|
46
|
-
const
|
|
231
|
+
const inputHandler = new InputHandler();
|
|
232
|
+
ensureHome();
|
|
233
|
+
let existingKeys = null;
|
|
234
|
+
try {
|
|
235
|
+
existingKeys = loadKeys();
|
|
236
|
+
} catch {
|
|
237
|
+
}
|
|
238
|
+
if (existingKeys) {
|
|
239
|
+
console.log("\nKeys found in ~/.buildwithnexus/.env.keys:");
|
|
240
|
+
if (existingKeys.ANTHROPIC_API_KEY) {
|
|
241
|
+
console.log(` ANTHROPIC_API_KEY: ${maskKey(existingKeys.ANTHROPIC_API_KEY)}`);
|
|
242
|
+
}
|
|
243
|
+
if (existingKeys.OPENAI_API_KEY) {
|
|
244
|
+
console.log(` OPENAI_API_KEY: ${maskKey(existingKeys.OPENAI_API_KEY)}`);
|
|
245
|
+
}
|
|
246
|
+
if (existingKeys.GOOGLE_API_KEY) {
|
|
247
|
+
console.log(` GOOGLE_API_KEY: ${maskKey(existingKeys.GOOGLE_API_KEY)}`);
|
|
248
|
+
}
|
|
249
|
+
const choice = await inputHandler.askQuestion(
|
|
250
|
+
"\nPress Enter to use cached keys, or type 'new' to reconfigure: "
|
|
251
|
+
);
|
|
252
|
+
if (choice.trim().toLowerCase() !== "new") {
|
|
253
|
+
process.env.ANTHROPIC_API_KEY = existingKeys.ANTHROPIC_API_KEY || "";
|
|
254
|
+
if (existingKeys.OPENAI_API_KEY) process.env.OPENAI_API_KEY = existingKeys.OPENAI_API_KEY;
|
|
255
|
+
if (existingKeys.GOOGLE_API_KEY) process.env.GOOGLE_API_KEY = existingKeys.GOOGLE_API_KEY;
|
|
256
|
+
console.log("Using cached keys.");
|
|
257
|
+
inputHandler.close();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
47
261
|
console.log(
|
|
48
|
-
"
|
|
262
|
+
"\nPlease provide your LLM API keys:\n(Stored securely in ~/.buildwithnexus/.env.keys)\n"
|
|
49
263
|
);
|
|
50
|
-
const anthropicKey = await askQuestion(
|
|
264
|
+
const anthropicKey = await inputHandler.askQuestion(
|
|
51
265
|
"ANTHROPIC_API_KEY (Claude - optional, press Enter to skip): "
|
|
52
266
|
);
|
|
53
|
-
const openaiKey = await askQuestion(
|
|
267
|
+
const openaiKey = await inputHandler.askQuestion(
|
|
54
268
|
"OPENAI_API_KEY (GPT - optional, press Enter to skip): "
|
|
55
269
|
);
|
|
56
|
-
const googleKey = await askQuestion(
|
|
270
|
+
const googleKey = await inputHandler.askQuestion(
|
|
57
271
|
"GOOGLE_API_KEY (Gemini - optional, press Enter to skip): "
|
|
58
272
|
);
|
|
59
273
|
if (!anthropicKey && !openaiKey && !googleKey) {
|
|
60
|
-
|
|
274
|
+
const anthropicStatus = anthropicKey ? "provided" : "empty";
|
|
275
|
+
const openaiStatus = openaiKey ? "provided" : "empty";
|
|
276
|
+
const googleStatus = googleKey ? "provided" : "empty";
|
|
277
|
+
console.log(
|
|
278
|
+
`Error: API keys status: Anthropic [${anthropicStatus}], OpenAI [${openaiStatus}], Google [${googleStatus}]. Please provide at least one.`
|
|
279
|
+
);
|
|
280
|
+
inputHandler.close();
|
|
61
281
|
return;
|
|
62
282
|
}
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
# Optional
|
|
78
|
-
# LOG_LEVEL=debug
|
|
79
|
-
`;
|
|
80
|
-
fs.writeFileSync(envPath, envContent, { mode: 384 });
|
|
81
|
-
reloadEnv(envPath);
|
|
82
|
-
console.log("Configuration saved to .env.local and loaded into environment.");
|
|
83
|
-
}
|
|
84
|
-
function setupInputHandling() {
|
|
85
|
-
return new Promise((resolve) => {
|
|
86
|
-
if (isSetup) {
|
|
87
|
-
resolve();
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
isSetup = true;
|
|
91
|
-
if (process.stdin.isTTY) {
|
|
92
|
-
resolve();
|
|
93
|
-
} else {
|
|
94
|
-
const rl = readline.createInterface({
|
|
95
|
-
input: process.stdin,
|
|
96
|
-
output: process.stdout,
|
|
97
|
-
terminal: false
|
|
98
|
-
});
|
|
99
|
-
rl.on("line", (line) => {
|
|
100
|
-
inputLines.push(line);
|
|
101
|
-
});
|
|
102
|
-
rl.on("close", () => {
|
|
103
|
-
resolve();
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
async function askQuestion(prompt) {
|
|
109
|
-
await setupInputHandling();
|
|
110
|
-
if (process.stdin.isTTY) {
|
|
111
|
-
return new Promise((resolve) => {
|
|
112
|
-
const rl = readline.createInterface({
|
|
113
|
-
input: process.stdin,
|
|
114
|
-
output: process.stdout
|
|
115
|
-
});
|
|
116
|
-
rl.question(prompt, (answer) => {
|
|
117
|
-
rl.close();
|
|
118
|
-
resolve(answer);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
} else {
|
|
122
|
-
process.stdout.write(prompt);
|
|
123
|
-
const answer = inputLines[lineIndex] || "";
|
|
124
|
-
lineIndex++;
|
|
125
|
-
console.log(answer);
|
|
126
|
-
return answer;
|
|
127
|
-
}
|
|
283
|
+
const masterSecret = generateMasterSecret();
|
|
284
|
+
const keys = {
|
|
285
|
+
ANTHROPIC_API_KEY: anthropicKey,
|
|
286
|
+
OPENAI_API_KEY: openaiKey || void 0,
|
|
287
|
+
GOOGLE_API_KEY: googleKey || void 0,
|
|
288
|
+
NEXUS_MASTER_SECRET: masterSecret
|
|
289
|
+
};
|
|
290
|
+
saveKeys(keys);
|
|
291
|
+
if (anthropicKey) process.env.ANTHROPIC_API_KEY = anthropicKey;
|
|
292
|
+
if (openaiKey) process.env.OPENAI_API_KEY = openaiKey;
|
|
293
|
+
if (googleKey) process.env.GOOGLE_API_KEY = googleKey;
|
|
294
|
+
console.log("Configuration saved to ~/.buildwithnexus/.env.keys and loaded into environment.");
|
|
295
|
+
inputHandler.close();
|
|
128
296
|
}
|
|
129
|
-
var
|
|
297
|
+
var InputHandler;
|
|
130
298
|
var init_init_command = __esm({
|
|
131
299
|
"src/cli/init-command.ts"() {
|
|
132
300
|
"use strict";
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
301
|
+
init_secrets();
|
|
302
|
+
InputHandler = class {
|
|
303
|
+
inputLines = [];
|
|
304
|
+
lineIndex = 0;
|
|
305
|
+
isSetup = false;
|
|
306
|
+
rl = null;
|
|
307
|
+
setupInputHandling() {
|
|
308
|
+
return new Promise((resolve) => {
|
|
309
|
+
if (this.isSetup) {
|
|
310
|
+
resolve();
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
this.isSetup = true;
|
|
314
|
+
if (process.stdin.isTTY) {
|
|
315
|
+
this.rl = readline.createInterface({
|
|
316
|
+
input: process.stdin,
|
|
317
|
+
output: process.stdout
|
|
318
|
+
});
|
|
319
|
+
resolve();
|
|
320
|
+
} else {
|
|
321
|
+
const rl = readline.createInterface({
|
|
322
|
+
input: process.stdin,
|
|
323
|
+
output: process.stdout,
|
|
324
|
+
terminal: false
|
|
325
|
+
});
|
|
326
|
+
rl.on("line", (line) => {
|
|
327
|
+
this.inputLines.push(line);
|
|
328
|
+
});
|
|
329
|
+
rl.on("close", () => {
|
|
330
|
+
resolve();
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
async askQuestion(prompt) {
|
|
336
|
+
await this.setupInputHandling();
|
|
337
|
+
if (process.stdin.isTTY) {
|
|
338
|
+
return new Promise((resolve) => {
|
|
339
|
+
this.rl.question(prompt, (answer) => {
|
|
340
|
+
resolve(answer);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
} else {
|
|
344
|
+
process.stdout.write(prompt);
|
|
345
|
+
const answer = this.inputLines[this.lineIndex] || "";
|
|
346
|
+
this.lineIndex++;
|
|
347
|
+
console.log(answer);
|
|
348
|
+
return answer;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
close() {
|
|
352
|
+
if (this.rl) {
|
|
353
|
+
this.rl.close();
|
|
354
|
+
this.rl = null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
};
|
|
137
358
|
}
|
|
138
359
|
});
|
|
139
360
|
|
|
140
|
-
// src/ui/
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
361
|
+
// src/ui/logger.ts
|
|
362
|
+
import chalk2 from "chalk";
|
|
363
|
+
var log;
|
|
364
|
+
var init_logger = __esm({
|
|
365
|
+
"src/ui/logger.ts"() {
|
|
366
|
+
"use strict";
|
|
367
|
+
log = {
|
|
368
|
+
step(msg) {
|
|
369
|
+
console.log(chalk2.cyan(" \u2192 ") + msg);
|
|
370
|
+
},
|
|
371
|
+
success(msg) {
|
|
372
|
+
console.log(chalk2.green(" \u2713 ") + msg);
|
|
373
|
+
},
|
|
374
|
+
error(msg) {
|
|
375
|
+
console.error(chalk2.red(" \u2717 ") + msg);
|
|
376
|
+
},
|
|
377
|
+
warn(msg) {
|
|
378
|
+
console.log(chalk2.yellow(" \u26A0 ") + msg);
|
|
379
|
+
},
|
|
380
|
+
dim(msg) {
|
|
381
|
+
console.log(chalk2.dim(" " + msg));
|
|
382
|
+
},
|
|
383
|
+
detail(label, value) {
|
|
384
|
+
console.log(chalk2.dim(" " + label + ": ") + value);
|
|
385
|
+
},
|
|
386
|
+
progress(current, total, label) {
|
|
387
|
+
const pct = Math.round(current / total * 100);
|
|
388
|
+
const filled = Math.round(current / total * 20);
|
|
389
|
+
const bar = chalk2.cyan("\u2588".repeat(filled)) + chalk2.dim("\u2591".repeat(20 - filled));
|
|
390
|
+
process.stdout.write(`\r [${bar}] ${chalk2.bold(`${pct}%`)} ${chalk2.dim(label)}`);
|
|
391
|
+
if (current >= total) process.stdout.write("\n");
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
}
|
|
147
395
|
});
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
import
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
396
|
+
|
|
397
|
+
// src/core/platform.ts
|
|
398
|
+
import os2 from "os";
|
|
399
|
+
function detectPlatform() {
|
|
400
|
+
const platform = os2.platform();
|
|
401
|
+
const arch = os2.arch();
|
|
402
|
+
if (platform === "darwin") {
|
|
403
|
+
return {
|
|
404
|
+
os: "mac",
|
|
405
|
+
arch: arch === "arm64" ? "arm64" : "x64",
|
|
406
|
+
dockerPlatform: arch === "arm64" ? "linux/arm64" : "linux/amd64"
|
|
407
|
+
};
|
|
160
408
|
}
|
|
409
|
+
if (platform === "linux") {
|
|
410
|
+
return {
|
|
411
|
+
os: "linux",
|
|
412
|
+
arch: arch === "arm64" ? "arm64" : "x64",
|
|
413
|
+
dockerPlatform: arch === "arm64" ? "linux/arm64" : "linux/amd64"
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
if (platform === "win32") {
|
|
417
|
+
return {
|
|
418
|
+
os: "windows",
|
|
419
|
+
arch: "x64",
|
|
420
|
+
dockerPlatform: "linux/amd64"
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
throw new Error(`Unsupported platform: ${platform} ${arch}`);
|
|
161
424
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
`));
|
|
166
|
-
}
|
|
167
|
-
function showPhase(phase, total, description) {
|
|
168
|
-
const progress = chalk5.cyan(`[${phase}/${total}]`);
|
|
169
|
-
console.log(`
|
|
170
|
-
${progress} ${chalk5.bold(description)}`);
|
|
171
|
-
}
|
|
172
|
-
function showSecurityPosture() {
|
|
173
|
-
const lines = [
|
|
174
|
-
"",
|
|
175
|
-
chalk5.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"),
|
|
176
|
-
chalk5.bold(" \u2551 ") + chalk5.bold.green("Security Posture") + chalk5.bold(" \u2551"),
|
|
177
|
-
chalk5.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"),
|
|
178
|
-
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" Triple-nested isolation: Host \u2192 VM \u2192 Docker \u2192 KVM".padEnd(54)) + chalk5.bold("\u2551"),
|
|
179
|
-
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" Network hardened: UFW deny-all, allow 22/80/443/4200".padEnd(54)) + chalk5.bold("\u2551"),
|
|
180
|
-
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" All databases encrypted at rest (AES-256-CBC)".padEnd(54)) + chalk5.bold("\u2551"),
|
|
181
|
-
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" API keys never embedded in VM \u2014 delivered via SCP".padEnd(54)) + chalk5.bold("\u2551"),
|
|
182
|
-
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" SSH-only communication (no exposed network ports)".padEnd(54)) + chalk5.bold("\u2551"),
|
|
183
|
-
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" DLP: secret detection, shell escaping, output redaction".padEnd(54)) + chalk5.bold("\u2551"),
|
|
184
|
-
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" HMAC integrity verification on all key files".padEnd(54)) + chalk5.bold("\u2551"),
|
|
185
|
-
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" Docker: --read-only, no-new-privileges, cap-drop=ALL".padEnd(54)) + chalk5.bold("\u2551"),
|
|
186
|
-
chalk5.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"),
|
|
187
|
-
chalk5.bold(" \u2551 ") + chalk5.dim("Full details: https://buildwithnexus.dev/security".padEnd(55)) + chalk5.bold("\u2551"),
|
|
188
|
-
chalk5.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"),
|
|
189
|
-
""
|
|
190
|
-
];
|
|
191
|
-
console.log(lines.join("\n"));
|
|
192
|
-
}
|
|
193
|
-
function showCompletion(urls) {
|
|
194
|
-
const lines = [
|
|
195
|
-
"",
|
|
196
|
-
chalk5.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"),
|
|
197
|
-
chalk5.green(" \u2551 ") + chalk5.bold.green("NEXUS Runtime is Live!") + chalk5.green(" \u2551"),
|
|
198
|
-
chalk5.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"),
|
|
199
|
-
chalk5.green(" \u2551 ") + chalk5.white(`Connect: ${urls.ssh}`.padEnd(55)) + chalk5.green("\u2551")
|
|
200
|
-
];
|
|
201
|
-
if (urls.remote) {
|
|
202
|
-
lines.push(chalk5.green(" \u2551 ") + chalk5.white(`Remote: ${urls.remote}`.padEnd(55)) + chalk5.green("\u2551"));
|
|
425
|
+
var init_platform = __esm({
|
|
426
|
+
"src/core/platform.ts"() {
|
|
427
|
+
"use strict";
|
|
203
428
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
chalk5.green(" \u2551 ") + chalk5.white(" buildwithnexus status - Check health".padEnd(55)) + chalk5.green("\u2551"),
|
|
210
|
-
chalk5.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"),
|
|
211
|
-
chalk5.green(" \u2551 ") + chalk5.dim("All commands:".padEnd(55)) + chalk5.green("\u2551"),
|
|
212
|
-
chalk5.green(" \u2551 ") + chalk5.white(" buildwithnexus stop/start/update/logs/ssh/destroy".padEnd(55)) + chalk5.green("\u2551"),
|
|
213
|
-
chalk5.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"),
|
|
214
|
-
""
|
|
215
|
-
);
|
|
216
|
-
console.log(lines.join("\n"));
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// src/core/utils.ts
|
|
432
|
+
function backoffMs(attempt) {
|
|
433
|
+
return Math.min(3e3 * Math.pow(2, attempt), 3e4);
|
|
217
434
|
}
|
|
218
|
-
var
|
|
219
|
-
|
|
220
|
-
"src/ui/banner.ts"() {
|
|
435
|
+
var init_utils = __esm({
|
|
436
|
+
"src/core/utils.ts"() {
|
|
221
437
|
"use strict";
|
|
222
|
-
BANNER = `
|
|
223
|
-
\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
|
|
224
|
-
\u2551 ${chalk5.bold.cyan("B U I L D W I T H N E X U S")} \u2551
|
|
225
|
-
\u2551 \u2551
|
|
226
|
-
\u2551 Autonomous Agent Orchestration \u2551
|
|
227
|
-
\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
|
|
228
|
-
`;
|
|
229
438
|
}
|
|
230
439
|
});
|
|
231
440
|
|
|
232
|
-
// src/
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
var TUI = class {
|
|
259
|
-
taskStartTime = 0;
|
|
260
|
-
eventCount = 0;
|
|
261
|
-
spinnerIndex = 0;
|
|
262
|
-
getSpinner() {
|
|
263
|
-
const frame = SPINNER_FRAMES[this.spinnerIndex % SPINNER_FRAMES.length];
|
|
264
|
-
this.spinnerIndex++;
|
|
265
|
-
return frame;
|
|
266
|
-
}
|
|
267
|
-
displayHeader(task, agent) {
|
|
268
|
-
console.clear();
|
|
269
|
-
const headerBox = this.makeRoundedBox(
|
|
270
|
-
colors.accent("\u{1F680} NEXUS - Autonomous Agent Orchestration"),
|
|
271
|
-
62,
|
|
272
|
-
colors.accent
|
|
273
|
-
);
|
|
274
|
-
console.log(headerBox);
|
|
275
|
-
console.log("");
|
|
276
|
-
console.log(colors.muted("Task") + colors.muted(": ") + chalk.white(task));
|
|
277
|
-
console.log(colors.muted("Agent") + colors.muted(": ") + colors.info(agent));
|
|
278
|
-
console.log("");
|
|
279
|
-
this.taskStartTime = Date.now();
|
|
441
|
+
// src/core/health.ts
|
|
442
|
+
var health_exports = {};
|
|
443
|
+
__export(health_exports, {
|
|
444
|
+
checkHealth: () => checkHealth,
|
|
445
|
+
waitForServer: () => waitForServer
|
|
446
|
+
});
|
|
447
|
+
async function checkHealth(vmRunning) {
|
|
448
|
+
const status = {
|
|
449
|
+
vmRunning,
|
|
450
|
+
sshReady: false,
|
|
451
|
+
dockerReady: false,
|
|
452
|
+
serverHealthy: false,
|
|
453
|
+
tunnelUrl: null,
|
|
454
|
+
dockerVersion: null,
|
|
455
|
+
serverVersion: null,
|
|
456
|
+
diskUsagePercent: null,
|
|
457
|
+
uptimeSeconds: null,
|
|
458
|
+
lastChecked: (/* @__PURE__ */ new Date()).toISOString()
|
|
459
|
+
};
|
|
460
|
+
if (!vmRunning) return status;
|
|
461
|
+
status.sshReady = true;
|
|
462
|
+
try {
|
|
463
|
+
const { stdout, code } = await dockerExec("docker version --format '{{.Server.Version}}'");
|
|
464
|
+
status.dockerReady = code === 0 && stdout.trim().length > 0;
|
|
465
|
+
if (status.dockerReady) status.dockerVersion = stdout.trim();
|
|
466
|
+
} catch {
|
|
280
467
|
}
|
|
281
|
-
|
|
282
|
-
|
|
468
|
+
try {
|
|
469
|
+
const { stdout, code } = await dockerExec("curl -sf http://localhost:4200/health");
|
|
470
|
+
status.serverHealthy = code === 0 && stdout.includes("ok");
|
|
471
|
+
if (status.serverHealthy) {
|
|
472
|
+
try {
|
|
473
|
+
const parsed = JSON.parse(stdout);
|
|
474
|
+
if (typeof parsed.version === "string") status.serverVersion = parsed.version;
|
|
475
|
+
} catch {
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
} catch {
|
|
283
479
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
480
|
+
try {
|
|
481
|
+
const { stdout } = await dockerExec("df / --output=pcent | tail -1 | tr -dc '0-9'");
|
|
482
|
+
const pct = parseInt(stdout.trim(), 10);
|
|
483
|
+
if (!isNaN(pct)) status.diskUsagePercent = pct;
|
|
484
|
+
} catch {
|
|
287
485
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
486
|
+
try {
|
|
487
|
+
const { stdout } = await dockerExec("awk '{print int($1)}' /proc/uptime 2>/dev/null");
|
|
488
|
+
const up = parseInt(stdout.trim(), 10);
|
|
489
|
+
if (!isNaN(up)) status.uptimeSeconds = up;
|
|
490
|
+
} catch {
|
|
291
491
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
});
|
|
299
|
-
const planBox = this.makeDoubleBox(planHeader, planLines.join("\n"), 62, colors.accent);
|
|
300
|
-
console.log(planBox);
|
|
301
|
-
console.log("");
|
|
492
|
+
try {
|
|
493
|
+
const { stdout } = await dockerExec("cat /home/nexus/.nexus/tunnel-url.txt 2>/dev/null");
|
|
494
|
+
if (stdout.includes("https://")) {
|
|
495
|
+
status.tunnelUrl = stdout.trim();
|
|
496
|
+
}
|
|
497
|
+
} catch {
|
|
302
498
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
return;
|
|
499
|
+
return status;
|
|
500
|
+
}
|
|
501
|
+
async function waitForServer(timeoutMs = 9e5) {
|
|
502
|
+
const start = Date.now();
|
|
503
|
+
let lastLog = 0;
|
|
504
|
+
let attempt = 0;
|
|
505
|
+
while (Date.now() - start < timeoutMs) {
|
|
506
|
+
try {
|
|
507
|
+
const { stdout, code } = await dockerExec("curl -sf http://localhost:4200/health");
|
|
508
|
+
if (code === 0 && stdout.includes("ok")) return true;
|
|
509
|
+
} catch {
|
|
313
510
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
511
|
+
const elapsed = Date.now() - start;
|
|
512
|
+
if (elapsed - lastLog >= 3e4) {
|
|
513
|
+
lastLog = elapsed;
|
|
514
|
+
try {
|
|
515
|
+
const { stdout } = await dockerExec("systemctl is-active nexus 2>/dev/null || echo 'starting...'");
|
|
516
|
+
process.stderr.write(`
|
|
517
|
+
[server ${Math.round(elapsed / 1e3)}s] ${stdout.trim().slice(0, 120)}
|
|
518
|
+
`);
|
|
519
|
+
} catch {
|
|
319
520
|
}
|
|
320
|
-
console.log(` ${STATUS_SYMBOLS.done} ${chalk.white(displayResult)}`);
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
const eventConfig = {
|
|
324
|
-
thought: { icon: "\u{1F4AD}", color: colors.info },
|
|
325
|
-
action: { icon: "\u26A1", color: colors.warning },
|
|
326
|
-
observation: { icon: "\u2713", color: colors.success },
|
|
327
|
-
started: { icon: "\u25B6", color: colors.info },
|
|
328
|
-
done: { icon: "\u2728", color: colors.success },
|
|
329
|
-
execution_complete: { icon: "\u2728", color: colors.success },
|
|
330
|
-
error: { icon: "\u2716", color: colors.error }
|
|
331
|
-
};
|
|
332
|
-
const config = eventConfig[type] || { icon: "\u25CF", color: colors.muted };
|
|
333
|
-
let displayContent = content;
|
|
334
|
-
if (displayContent.length > 100) {
|
|
335
|
-
displayContent = displayContent.substring(0, 97) + "...";
|
|
336
521
|
}
|
|
337
|
-
|
|
522
|
+
const delay = backoffMs(attempt++);
|
|
523
|
+
const remaining = timeoutMs - (Date.now() - start);
|
|
524
|
+
if (remaining <= 0) break;
|
|
525
|
+
await new Promise((r) => setTimeout(r, Math.min(delay, remaining)));
|
|
338
526
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
console.log(` ${colors.success("\u2502")} ${chalk.white(line)}`);
|
|
347
|
-
}
|
|
348
|
-
console.log("");
|
|
349
|
-
console.log(` ${colors.muted(`${todosCompleted} subtask(s) completed`)}`);
|
|
350
|
-
console.log("");
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
var init_health = __esm({
|
|
530
|
+
"src/core/health.ts"() {
|
|
531
|
+
"use strict";
|
|
532
|
+
init_docker();
|
|
533
|
+
init_utils();
|
|
351
534
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// src/core/docker.ts
|
|
538
|
+
var docker_exports = {};
|
|
539
|
+
__export(docker_exports, {
|
|
540
|
+
dockerExec: () => dockerExec,
|
|
541
|
+
imageExistsLocally: () => imageExistsLocally,
|
|
542
|
+
installDocker: () => installDocker,
|
|
543
|
+
isDockerInstalled: () => isDockerInstalled,
|
|
544
|
+
isNexusRunning: () => isNexusRunning,
|
|
545
|
+
launchNexus: () => launchNexus,
|
|
546
|
+
pullImage: () => pullImage,
|
|
547
|
+
startBackend: () => startBackend,
|
|
548
|
+
startNexus: () => startNexus,
|
|
549
|
+
stopNexus: () => stopNexus
|
|
550
|
+
});
|
|
551
|
+
import { existsSync } from "fs";
|
|
552
|
+
import { execa } from "execa";
|
|
553
|
+
async function isDockerInstalled() {
|
|
554
|
+
try {
|
|
555
|
+
await execa("docker", ["info"]);
|
|
556
|
+
return true;
|
|
557
|
+
} catch {
|
|
558
|
+
return false;
|
|
357
559
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
560
|
+
}
|
|
561
|
+
async function isDockerInstalledButNotRunning() {
|
|
562
|
+
try {
|
|
563
|
+
await execa("docker", ["--version"]);
|
|
564
|
+
return true;
|
|
565
|
+
} catch {
|
|
566
|
+
return false;
|
|
364
567
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
568
|
+
}
|
|
569
|
+
function dockerDesktopExists() {
|
|
570
|
+
return existsSync(DOCKER_DESKTOP_APP_PATH);
|
|
571
|
+
}
|
|
572
|
+
async function ensureHomebrew() {
|
|
573
|
+
try {
|
|
574
|
+
await execa("which", ["brew"]);
|
|
575
|
+
log.dim("Homebrew is already installed.");
|
|
576
|
+
return;
|
|
577
|
+
} catch {
|
|
374
578
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
BRAINSTORM: { icon: "\u{1F4A1}", color: chalk.blue }
|
|
384
|
-
};
|
|
385
|
-
const parts = modes.map((m) => {
|
|
386
|
-
const config = modeConfig[m];
|
|
387
|
-
const label = `${config.icon} ${m}`;
|
|
388
|
-
if (m === current) {
|
|
389
|
-
return config.color.bold(label);
|
|
390
|
-
}
|
|
391
|
-
return colors.muted(label);
|
|
579
|
+
log.step("Installing Homebrew...");
|
|
580
|
+
try {
|
|
581
|
+
await execa("/bin/bash", [
|
|
582
|
+
"-c",
|
|
583
|
+
"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
584
|
+
], {
|
|
585
|
+
stdio: "inherit",
|
|
586
|
+
env: { ...process.env, NONINTERACTIVE: "1" }
|
|
392
587
|
});
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
588
|
+
} catch {
|
|
589
|
+
throw new Error(
|
|
590
|
+
'Failed to install Homebrew automatically.\n\n Install Homebrew manually:\n /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"\n\n Then re-run:\n buildwithnexus init'
|
|
591
|
+
);
|
|
396
592
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
BUILD: {
|
|
405
|
-
icon: "\u2699\uFE0F",
|
|
406
|
-
desc: "Execute with live streaming",
|
|
407
|
-
color: colors.success
|
|
408
|
-
},
|
|
409
|
-
BRAINSTORM: {
|
|
410
|
-
icon: "\u{1F4A1}",
|
|
411
|
-
desc: "Free-form Q&A and exploration",
|
|
412
|
-
color: chalk.blue
|
|
413
|
-
}
|
|
414
|
-
};
|
|
415
|
-
const c = config[mode];
|
|
416
|
-
console.log("");
|
|
417
|
-
console.log(c.color.bold(`${c.icon} ${mode}`));
|
|
418
|
-
console.log(colors.muted(c.desc));
|
|
419
|
-
console.log("");
|
|
593
|
+
try {
|
|
594
|
+
await execa("which", ["brew"]);
|
|
595
|
+
} catch {
|
|
596
|
+
const armPath = "/opt/homebrew/bin";
|
|
597
|
+
const intelPath = "/usr/local/bin";
|
|
598
|
+
process.env.PATH = `${armPath}:${intelPath}:${process.env.PATH}`;
|
|
599
|
+
log.dim("Added Homebrew paths to PATH for this session.");
|
|
420
600
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
const taskPreview = task.length > 45 ? task.substring(0, 42) + "..." : task;
|
|
428
|
-
console.log("");
|
|
429
|
-
console.log(
|
|
430
|
-
colors.muted("Suggested: ") + modeColor[mode].bold(mode) + colors.muted(` for "${taskPreview}"`)
|
|
601
|
+
try {
|
|
602
|
+
await execa("brew", ["--version"]);
|
|
603
|
+
log.success("Homebrew installed successfully.");
|
|
604
|
+
} catch {
|
|
605
|
+
throw new Error(
|
|
606
|
+
"Homebrew was installed but is not available on PATH.\n\n Try opening a new terminal and re-running:\n buildwithnexus init"
|
|
431
607
|
);
|
|
432
608
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
609
|
+
}
|
|
610
|
+
async function installDocker(platform) {
|
|
611
|
+
const p = platform ?? detectPlatform();
|
|
612
|
+
switch (p.os) {
|
|
613
|
+
case "mac": {
|
|
614
|
+
if (await isDockerInstalled()) {
|
|
615
|
+
log.success("Docker is already running.");
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (dockerDesktopExists()) {
|
|
619
|
+
log.step(`Docker Desktop found at ${DOCKER_DESKTOP_APP_PATH} but not running. Attempting to start...`);
|
|
620
|
+
let launched = false;
|
|
621
|
+
log.dim(`Trying: open ${DOCKER_DESKTOP_APP_PATH}`);
|
|
622
|
+
try {
|
|
623
|
+
await execa("open", [DOCKER_DESKTOP_APP_PATH]);
|
|
624
|
+
launched = true;
|
|
625
|
+
log.dim(`Launch command succeeded via ${DOCKER_DESKTOP_APP_PATH}`);
|
|
626
|
+
} catch {
|
|
627
|
+
log.warn(`Could not launch via ${DOCKER_DESKTOP_APP_PATH} \u2014 trying fallback...`);
|
|
628
|
+
}
|
|
629
|
+
if (!launched) {
|
|
630
|
+
log.dim("Trying: open -a Docker");
|
|
631
|
+
try {
|
|
632
|
+
await execa("open", ["-a", "Docker"]);
|
|
633
|
+
launched = true;
|
|
634
|
+
log.dim("Launch command succeeded via open -a Docker");
|
|
635
|
+
} catch {
|
|
636
|
+
log.warn("Both launch attempts failed.");
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (launched) {
|
|
640
|
+
log.step("Docker Desktop is starting up. Waiting for the daemon to be ready (up to 120s)...");
|
|
641
|
+
try {
|
|
642
|
+
await waitForDockerDaemon(12e4);
|
|
643
|
+
return;
|
|
644
|
+
} catch {
|
|
645
|
+
log.warn("Docker Desktop was launched but the daemon did not become ready in time.");
|
|
646
|
+
}
|
|
647
|
+
} else {
|
|
648
|
+
log.warn("Could not launch Docker Desktop. Will fall back to reinstalling via Homebrew.");
|
|
649
|
+
}
|
|
441
650
|
} else {
|
|
442
|
-
|
|
651
|
+
log.step(`Docker Desktop not found at ${DOCKER_DESKTOP_APP_PATH}.`);
|
|
443
652
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
lines.push(borderColor("\u2551") + chalk.bold(titlePadded) + borderColor("\u2551"));
|
|
465
|
-
lines.push(borderColor("\u2560" + "\u2550".repeat(innerWidth) + "\u2563"));
|
|
466
|
-
const contentLines = content.split("\n");
|
|
467
|
-
for (const line of contentLines) {
|
|
468
|
-
const padded = line.substring(0, innerWidth - 2).padEnd(innerWidth - 2);
|
|
469
|
-
lines.push(borderColor("\u2551") + " " + padded + borderColor("\u2551"));
|
|
470
|
-
}
|
|
471
|
-
lines.push(borderColor("\u255A" + "\u2550".repeat(innerWidth) + "\u255D"));
|
|
472
|
-
return lines.join("\n");
|
|
473
|
-
}
|
|
474
|
-
};
|
|
475
|
-
var tui = new TUI();
|
|
476
|
-
|
|
477
|
-
// src/cli/run-command.ts
|
|
478
|
-
init_config();
|
|
479
|
-
async function runCommand(task, options) {
|
|
480
|
-
const backendUrl = process.env.BACKEND_URL || "http://localhost:4200";
|
|
481
|
-
tui.displayHeader(task, options.agent);
|
|
482
|
-
tui.displayConnecting();
|
|
483
|
-
try {
|
|
484
|
-
let healthOk = false;
|
|
485
|
-
try {
|
|
486
|
-
const healthResponse = await fetch(`${backendUrl}/health`);
|
|
487
|
-
healthOk = healthResponse.ok;
|
|
488
|
-
} catch {
|
|
489
|
-
}
|
|
490
|
-
if (!healthOk) {
|
|
491
|
-
console.error(
|
|
492
|
-
"Backend not responding. Start it with:\n buildwithnexus server"
|
|
493
|
-
);
|
|
494
|
-
process.exit(1);
|
|
495
|
-
}
|
|
496
|
-
const keys = loadApiKeys();
|
|
497
|
-
const response = await fetch(`${backendUrl}/api/run`, {
|
|
498
|
-
method: "POST",
|
|
499
|
-
headers: { "Content-Type": "application/json" },
|
|
500
|
-
body: JSON.stringify({
|
|
501
|
-
task,
|
|
502
|
-
agent_role: options.agent,
|
|
503
|
-
agent_goal: options.goal || "",
|
|
504
|
-
api_key: keys.anthropic || "",
|
|
505
|
-
openai_api_key: keys.openai || "",
|
|
506
|
-
google_api_key: keys.google || ""
|
|
507
|
-
})
|
|
508
|
-
});
|
|
509
|
-
if (!response.ok) {
|
|
510
|
-
console.error("Backend error");
|
|
511
|
-
console.error(await response.text());
|
|
512
|
-
process.exit(1);
|
|
513
|
-
}
|
|
514
|
-
const { run_id } = await response.json();
|
|
515
|
-
tui.displayConnected(run_id);
|
|
516
|
-
tui.displayStreamStart();
|
|
517
|
-
const eventSourceUrl = `${backendUrl}/api/stream/${run_id}`;
|
|
518
|
-
try {
|
|
519
|
-
const response2 = await fetch(eventSourceUrl);
|
|
520
|
-
const reader = response2.body?.getReader();
|
|
521
|
-
const decoder = new TextDecoder();
|
|
522
|
-
if (!reader) {
|
|
523
|
-
throw new Error("No response body");
|
|
653
|
+
log.step("Installing Docker Desktop via Homebrew...");
|
|
654
|
+
await ensureHomebrew();
|
|
655
|
+
try {
|
|
656
|
+
await execa("brew", ["install", "--cask", "docker"], {
|
|
657
|
+
stdio: "inherit",
|
|
658
|
+
timeout: 6e4
|
|
659
|
+
});
|
|
660
|
+
} catch (err) {
|
|
661
|
+
const e = err;
|
|
662
|
+
if (e.killed && e.signal === "SIGINT") {
|
|
663
|
+
throw new Error("Docker installation cancelled by user (Ctrl+C)");
|
|
664
|
+
}
|
|
665
|
+
if (e.timedOut) {
|
|
666
|
+
throw new Error(
|
|
667
|
+
"Docker installation via Homebrew timed out after 60 seconds.\n\n The password prompt may be waiting for input. Try installing manually:\n brew install --cask docker\n\n After installing, re-run:\n buildwithnexus init"
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
throw new Error(
|
|
671
|
+
"Failed to install Docker via Homebrew.\n\n Try installing Docker Desktop manually:\n https://www.docker.com/products/docker-desktop\n\n After installing, re-run:\n buildwithnexus init"
|
|
672
|
+
);
|
|
524
673
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
tui.displayError(content);
|
|
544
|
-
process.exit(1);
|
|
545
|
-
} else {
|
|
546
|
-
tui.displayEvent(type, { content });
|
|
547
|
-
}
|
|
548
|
-
} catch {
|
|
549
|
-
}
|
|
550
|
-
}
|
|
674
|
+
log.step("Launching Docker Desktop...");
|
|
675
|
+
let postInstallLaunched = false;
|
|
676
|
+
log.dim(`Trying: open ${DOCKER_DESKTOP_APP_PATH}`);
|
|
677
|
+
try {
|
|
678
|
+
await execa("open", [DOCKER_DESKTOP_APP_PATH]);
|
|
679
|
+
postInstallLaunched = true;
|
|
680
|
+
log.dim(`Launch command succeeded via ${DOCKER_DESKTOP_APP_PATH}`);
|
|
681
|
+
} catch {
|
|
682
|
+
log.warn(`Could not launch via ${DOCKER_DESKTOP_APP_PATH} \u2014 trying fallback...`);
|
|
683
|
+
}
|
|
684
|
+
if (!postInstallLaunched) {
|
|
685
|
+
log.dim("Trying: open -a Docker");
|
|
686
|
+
try {
|
|
687
|
+
await execa("open", ["-a", "Docker"]);
|
|
688
|
+
postInstallLaunched = true;
|
|
689
|
+
log.dim("Launch command succeeded via open -a Docker");
|
|
690
|
+
} catch {
|
|
691
|
+
log.warn("Both launch attempts failed after install.");
|
|
551
692
|
}
|
|
552
693
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
694
|
+
if (!postInstallLaunched) {
|
|
695
|
+
throw new Error(
|
|
696
|
+
"Docker Desktop was installed but could not be started automatically.\n\n Next steps:\n 1. Open Docker Desktop manually from your Applications folder\n 2. Wait for the whale icon to appear in the menu bar\n 3. Re-run: buildwithnexus init"
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
log.step("Docker Desktop is starting up. Waiting for the daemon to be ready (up to 120s)...");
|
|
700
|
+
await waitForDockerDaemon(12e4);
|
|
701
|
+
break;
|
|
558
702
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
703
|
+
case "linux": {
|
|
704
|
+
const linuxBinaryExists = await isDockerInstalledButNotRunning();
|
|
705
|
+
if (linuxBinaryExists) {
|
|
706
|
+
log.step("Docker is installed but the daemon is not running.");
|
|
707
|
+
log.step("Starting Docker daemon...");
|
|
708
|
+
try {
|
|
709
|
+
await execa("sudo", ["systemctl", "start", "docker"], { stdio: "inherit" });
|
|
710
|
+
log.dim("Started Docker daemon via systemctl.");
|
|
711
|
+
} catch {
|
|
712
|
+
try {
|
|
713
|
+
await execa("sudo", ["service", "docker", "start"], { stdio: "inherit" });
|
|
714
|
+
log.dim("Started Docker daemon via service command.");
|
|
715
|
+
} catch {
|
|
716
|
+
throw new Error(
|
|
717
|
+
"Docker is installed but the daemon could not be started.\n\n Try starting it manually:\n sudo systemctl start docker\n\n Then re-run:\n buildwithnexus init"
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
log.step("Waiting for Docker...");
|
|
722
|
+
await waitForDockerDaemon(3e4);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
log.step("Installing Docker...");
|
|
726
|
+
log.warn("This may require your sudo password.");
|
|
727
|
+
log.dim("Running official Docker install script from https://get.docker.com ...");
|
|
728
|
+
try {
|
|
729
|
+
const { stdout: script } = await execa("curl", ["-fsSL", "https://get.docker.com"]);
|
|
730
|
+
await execa("sudo", ["sh", "-c", script], { stdio: "inherit" });
|
|
731
|
+
log.success("Docker installed successfully.");
|
|
732
|
+
} catch {
|
|
733
|
+
throw new Error(
|
|
734
|
+
"Failed to install Docker on Linux.\n\n Try installing manually:\n curl -fsSL https://get.docker.com | sudo sh\n\n After installing, re-run:\n buildwithnexus init"
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
log.dim("Adding current user to docker group...");
|
|
738
|
+
try {
|
|
739
|
+
const user = (await execa("whoami")).stdout.trim();
|
|
740
|
+
await execa("sudo", ["usermod", "-aG", "docker", user]);
|
|
741
|
+
log.dim(`Added user '${user}' to docker group (may require re-login for effect).`);
|
|
742
|
+
} catch {
|
|
743
|
+
log.warn("Could not add user to docker group. You may need sudo for docker commands.");
|
|
744
|
+
}
|
|
745
|
+
log.step("Starting Docker daemon...");
|
|
746
|
+
try {
|
|
747
|
+
await execa("sudo", ["systemctl", "start", "docker"], { stdio: "inherit" });
|
|
748
|
+
log.dim("Started Docker daemon via systemctl.");
|
|
749
|
+
} catch {
|
|
750
|
+
try {
|
|
751
|
+
await execa("sudo", ["service", "docker", "start"], { stdio: "inherit" });
|
|
752
|
+
log.dim("Started Docker daemon via service command.");
|
|
753
|
+
} catch {
|
|
754
|
+
throw new Error(
|
|
755
|
+
"Docker was installed but the daemon could not be started.\n\n Try starting it manually:\n sudo systemctl start docker\n\n Then re-run:\n buildwithnexus init"
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
log.step("Waiting for Docker...");
|
|
760
|
+
await waitForDockerDaemon(3e4);
|
|
761
|
+
break;
|
|
762
|
+
}
|
|
763
|
+
case "windows": {
|
|
764
|
+
const winBinaryExists = await isDockerInstalledButNotRunning();
|
|
765
|
+
if (winBinaryExists) {
|
|
766
|
+
log.step("Docker Desktop is installed but not running. Attempting to start...");
|
|
767
|
+
log.step("Launching Docker...");
|
|
768
|
+
try {
|
|
769
|
+
await execa("powershell", ["-Command", "Start-Process 'Docker Desktop'"], { stdio: "inherit" });
|
|
770
|
+
log.dim("Docker Desktop launch command sent.");
|
|
771
|
+
} catch {
|
|
772
|
+
log.warn("Could not launch Docker Desktop automatically. It may need to be started manually.");
|
|
773
|
+
}
|
|
774
|
+
log.step("Waiting for Docker...");
|
|
775
|
+
try {
|
|
776
|
+
await waitForDockerDaemon(12e4);
|
|
777
|
+
} catch {
|
|
778
|
+
throw new Error(
|
|
779
|
+
"Docker Desktop did not start within 120 seconds.\n\n Next steps:\n 1. Open Docker Desktop manually from the Start Menu\n 2. Wait for the whale icon to appear in the system tray\n 3. Re-run: buildwithnexus init"
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
log.step("Installing Docker Desktop...");
|
|
785
|
+
let installed = false;
|
|
786
|
+
try {
|
|
787
|
+
await execa("choco", ["--version"]);
|
|
788
|
+
log.dim("Chocolatey detected. Installing Docker Desktop via choco...");
|
|
789
|
+
try {
|
|
790
|
+
await execa("choco", ["install", "docker-desktop", "-y"], { stdio: "inherit" });
|
|
791
|
+
installed = true;
|
|
792
|
+
log.success("Docker Desktop installed via Chocolatey.");
|
|
793
|
+
} catch {
|
|
794
|
+
log.warn("Chocolatey install failed. Falling back to direct download...");
|
|
795
|
+
}
|
|
796
|
+
} catch {
|
|
797
|
+
log.dim("Chocolatey not found. Using direct download...");
|
|
798
|
+
}
|
|
799
|
+
if (!installed) {
|
|
800
|
+
log.dim("Downloading Docker Desktop installer from docker.com...");
|
|
801
|
+
try {
|
|
802
|
+
await execa("powershell", [
|
|
803
|
+
"-Command",
|
|
804
|
+
"Invoke-WebRequest -Uri 'https://desktop.docker.com/win/main/amd64/Docker Desktop Installer.exe' -OutFile '$env:TEMP\\DockerInstaller.exe'; & '$env:TEMP\\DockerInstaller.exe' Install --quiet; Remove-Item '$env:TEMP\\DockerInstaller.exe' -Force -ErrorAction SilentlyContinue"
|
|
805
|
+
], { stdio: "inherit" });
|
|
806
|
+
installed = true;
|
|
807
|
+
log.success("Docker Desktop installed via direct download.");
|
|
808
|
+
} catch {
|
|
809
|
+
throw new Error(
|
|
810
|
+
"Failed to install Docker Desktop on Windows.\n\n Please install Docker Desktop manually:\n 1. Download from https://www.docker.com/products/docker-desktop\n 2. Run the installer and follow the prompts\n 3. Start Docker Desktop\n 4. Re-run: buildwithnexus init"
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
log.step("Launching Docker...");
|
|
815
|
+
try {
|
|
816
|
+
await execa("powershell", ["-Command", "Start-Process 'Docker Desktop'"], { stdio: "inherit" });
|
|
817
|
+
log.dim("Docker Desktop launch command sent.");
|
|
818
|
+
} catch {
|
|
819
|
+
log.warn("Could not launch Docker Desktop automatically after install.");
|
|
820
|
+
}
|
|
821
|
+
log.step("Waiting for Docker...");
|
|
822
|
+
try {
|
|
823
|
+
await waitForDockerDaemon(12e4);
|
|
824
|
+
} catch {
|
|
825
|
+
throw new Error(
|
|
826
|
+
"Docker Desktop was installed but did not start within 120 seconds.\n\n Next steps:\n 1. You may need to restart your computer for Docker to work\n 2. Open Docker Desktop from the Start Menu\n 3. Wait for the whale icon to appear in the system tray\n 4. Re-run: buildwithnexus init"
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
break;
|
|
830
|
+
}
|
|
831
|
+
default:
|
|
832
|
+
throw new Error(`Unsupported platform: ${p.os}`);
|
|
833
|
+
}
|
|
586
834
|
}
|
|
835
|
+
async function waitForDockerDaemon(timeoutMs) {
|
|
836
|
+
const start = Date.now();
|
|
837
|
+
log.step("Waiting for Docker daemon...");
|
|
838
|
+
while (Date.now() - start < timeoutMs) {
|
|
839
|
+
try {
|
|
840
|
+
await execa("docker", ["info"]);
|
|
841
|
+
log.success("Docker daemon is ready");
|
|
842
|
+
return;
|
|
843
|
+
} catch {
|
|
844
|
+
}
|
|
845
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
846
|
+
}
|
|
847
|
+
throw new Error(
|
|
848
|
+
`Docker daemon did not become ready within ${Math.round(timeoutMs / 1e3)}s.
|
|
587
849
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
850
|
+
Please ensure Docker is running, then re-run:
|
|
851
|
+
buildwithnexus init`
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
async function imageExistsLocally(image, tag) {
|
|
855
|
+
const ref = `${image}:${tag}`;
|
|
856
|
+
try {
|
|
857
|
+
await execa("docker", ["image", "inspect", ref]);
|
|
858
|
+
return true;
|
|
859
|
+
} catch {
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
async function pullImage(image, tag) {
|
|
864
|
+
const ref = `${image}:${tag}`;
|
|
865
|
+
log.step(`Pulling image ${ref}...`);
|
|
866
|
+
try {
|
|
867
|
+
await execa("docker", ["pull", ref], { stdio: "inherit" });
|
|
868
|
+
log.success(`Image ${ref} pulled`);
|
|
869
|
+
} catch (err) {
|
|
870
|
+
log.error(`Failed to pull image ${ref}`);
|
|
871
|
+
throw err;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
async function startNexus(keys, config) {
|
|
875
|
+
log.step("Starting NEXUS container...");
|
|
876
|
+
try {
|
|
877
|
+
await execa("docker", [
|
|
878
|
+
"run",
|
|
879
|
+
"-d",
|
|
880
|
+
"--name",
|
|
881
|
+
CONTAINER_NAME,
|
|
882
|
+
"-e",
|
|
883
|
+
`ANTHROPIC_API_KEY=${keys.anthropic}`,
|
|
884
|
+
"-e",
|
|
885
|
+
`OPENAI_API_KEY=${keys.openai}`,
|
|
886
|
+
"-p",
|
|
887
|
+
`${config.port}:${config.port}`,
|
|
888
|
+
"buildwithnexus/nexus:latest"
|
|
889
|
+
]);
|
|
890
|
+
log.success(`NEXUS container started on port ${config.port}`);
|
|
891
|
+
} catch (err) {
|
|
892
|
+
log.error("Failed to start NEXUS container");
|
|
893
|
+
throw err;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
async function stopNexus() {
|
|
897
|
+
log.step("Stopping NEXUS container...");
|
|
898
|
+
try {
|
|
899
|
+
await execa("docker", ["rm", "-f", CONTAINER_NAME]);
|
|
900
|
+
log.success("NEXUS container stopped and removed");
|
|
901
|
+
} catch (err) {
|
|
902
|
+
log.error("Failed to stop NEXUS container");
|
|
903
|
+
throw err;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
async function dockerExec(command) {
|
|
907
|
+
try {
|
|
908
|
+
const { stdout, stderr } = await execa("docker", ["exec", CONTAINER_NAME, "sh", "-c", command]);
|
|
909
|
+
return { stdout, stderr, code: 0 };
|
|
910
|
+
} catch (err) {
|
|
911
|
+
const e = err;
|
|
912
|
+
return { stdout: e.stdout ?? "", stderr: e.stderr ?? "", code: e.exitCode ?? 1 };
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
async function startBackend() {
|
|
916
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
917
|
+
const os5 = await import("os");
|
|
918
|
+
const path11 = await import("path");
|
|
919
|
+
const nexusDir = path11.join(os5.homedir(), "Projects", "nexus");
|
|
920
|
+
log.step(`Starting Nexus backend from ${nexusDir}...`);
|
|
921
|
+
const child = spawn2("python", ["-m", "src.deep_agents_server"], {
|
|
922
|
+
cwd: nexusDir,
|
|
923
|
+
detached: true,
|
|
924
|
+
stdio: "ignore",
|
|
925
|
+
env: { ...process.env }
|
|
926
|
+
});
|
|
927
|
+
child.unref();
|
|
928
|
+
log.success("Nexus backend process started");
|
|
929
|
+
}
|
|
930
|
+
async function launchNexus(keys, config, opts) {
|
|
931
|
+
const { healthTimeoutMs = 6e4, stopExisting = true } = opts ?? {};
|
|
932
|
+
if (stopExisting && await isNexusRunning()) {
|
|
933
|
+
await stopNexus();
|
|
934
|
+
}
|
|
935
|
+
await startNexus(keys, config);
|
|
936
|
+
if (healthTimeoutMs <= 0) return true;
|
|
937
|
+
const { waitForServer: waitForServer2 } = await Promise.resolve().then(() => (init_health(), health_exports));
|
|
938
|
+
return waitForServer2(healthTimeoutMs);
|
|
939
|
+
}
|
|
940
|
+
async function isNexusRunning() {
|
|
941
|
+
try {
|
|
942
|
+
const { stdout } = await execa("docker", [
|
|
943
|
+
"ps",
|
|
944
|
+
"--filter",
|
|
945
|
+
`name=^/${CONTAINER_NAME}$`,
|
|
946
|
+
"--format",
|
|
947
|
+
"{{.Names}}"
|
|
948
|
+
]);
|
|
949
|
+
return stdout.trim() === CONTAINER_NAME;
|
|
950
|
+
} catch {
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
var CONTAINER_NAME, DOCKER_DESKTOP_APP_PATH;
|
|
955
|
+
var init_docker = __esm({
|
|
956
|
+
"src/core/docker.ts"() {
|
|
957
|
+
"use strict";
|
|
958
|
+
init_logger();
|
|
959
|
+
init_platform();
|
|
960
|
+
CONTAINER_NAME = "nexus";
|
|
961
|
+
DOCKER_DESKTOP_APP_PATH = "/Applications/Docker.app";
|
|
962
|
+
}
|
|
963
|
+
});
|
|
591
964
|
|
|
592
|
-
// src/
|
|
593
|
-
var
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
"
|
|
621
|
-
"
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
"
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
"
|
|
630
|
-
"
|
|
631
|
-
"
|
|
632
|
-
"
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
"
|
|
636
|
-
"brainstorm",
|
|
637
|
-
"explore",
|
|
638
|
-
"wonder",
|
|
639
|
-
"might",
|
|
640
|
-
"could",
|
|
641
|
-
"would",
|
|
642
|
-
"how about",
|
|
643
|
-
"what if",
|
|
644
|
-
"options",
|
|
645
|
-
"alternatives",
|
|
646
|
-
"thoughts"
|
|
647
|
-
];
|
|
648
|
-
function classifyIntent(task) {
|
|
649
|
-
const lower = task.toLowerCase().trim();
|
|
650
|
-
let planScore = 0;
|
|
651
|
-
let buildScore = 0;
|
|
652
|
-
let brainstormScore = 0;
|
|
653
|
-
for (const kw of PLAN_KEYWORDS) {
|
|
654
|
-
if (lower.includes(kw)) planScore++;
|
|
965
|
+
// src/ui/banner.ts
|
|
966
|
+
var banner_exports = {};
|
|
967
|
+
__export(banner_exports, {
|
|
968
|
+
showCompletion: () => showCompletion,
|
|
969
|
+
showPhase: () => showPhase,
|
|
970
|
+
showSecurityPosture: () => showSecurityPosture
|
|
971
|
+
});
|
|
972
|
+
import chalk5 from "chalk";
|
|
973
|
+
function showPhase(phase, total, description) {
|
|
974
|
+
const progress = chalk5.cyan(`[${phase}/${total}]`);
|
|
975
|
+
console.log(`
|
|
976
|
+
${progress} ${chalk5.bold(description)}`);
|
|
977
|
+
}
|
|
978
|
+
function showSecurityPosture() {
|
|
979
|
+
const lines = [
|
|
980
|
+
"",
|
|
981
|
+
chalk5.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"),
|
|
982
|
+
chalk5.bold(" \u2551 ") + chalk5.bold.green("Security Posture") + chalk5.bold(" \u2551"),
|
|
983
|
+
chalk5.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"),
|
|
984
|
+
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" Triple-nested isolation: Host \u2192 VM \u2192 Docker \u2192 KVM".padEnd(54)) + chalk5.bold("\u2551"),
|
|
985
|
+
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" Network hardened: UFW deny-all, allow 22/80/443/4200".padEnd(54)) + chalk5.bold("\u2551"),
|
|
986
|
+
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" All databases encrypted at rest (AES-256-CBC)".padEnd(54)) + chalk5.bold("\u2551"),
|
|
987
|
+
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" API keys never embedded in VM \u2014 delivered via SCP".padEnd(54)) + chalk5.bold("\u2551"),
|
|
988
|
+
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" SSH-only communication (no exposed network ports)".padEnd(54)) + chalk5.bold("\u2551"),
|
|
989
|
+
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" DLP: secret detection, shell escaping, output redaction".padEnd(54)) + chalk5.bold("\u2551"),
|
|
990
|
+
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" HMAC integrity verification on all key files".padEnd(54)) + chalk5.bold("\u2551"),
|
|
991
|
+
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" Docker: --read-only, no-new-privileges, cap-drop=ALL".padEnd(54)) + chalk5.bold("\u2551"),
|
|
992
|
+
chalk5.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"),
|
|
993
|
+
chalk5.bold(" \u2551 ") + chalk5.dim("Full details: https://buildwithnexus.dev/security".padEnd(55)) + chalk5.bold("\u2551"),
|
|
994
|
+
chalk5.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"),
|
|
995
|
+
""
|
|
996
|
+
];
|
|
997
|
+
console.log(lines.join("\n"));
|
|
998
|
+
}
|
|
999
|
+
function showCompletion(urls) {
|
|
1000
|
+
const lines = [
|
|
1001
|
+
"",
|
|
1002
|
+
chalk5.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"),
|
|
1003
|
+
chalk5.green(" \u2551 ") + chalk5.bold.green("NEXUS Runtime is Live!") + chalk5.green(" \u2551"),
|
|
1004
|
+
chalk5.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"),
|
|
1005
|
+
chalk5.green(" \u2551 ") + chalk5.white(`Connect: ${urls.ssh}`.padEnd(55)) + chalk5.green("\u2551")
|
|
1006
|
+
];
|
|
1007
|
+
if (urls.remote) {
|
|
1008
|
+
lines.push(chalk5.green(" \u2551 ") + chalk5.white(`Remote: ${urls.remote}`.padEnd(55)) + chalk5.green("\u2551"));
|
|
655
1009
|
}
|
|
656
|
-
|
|
657
|
-
|
|
1010
|
+
lines.push(
|
|
1011
|
+
chalk5.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"),
|
|
1012
|
+
chalk5.green(" \u2551 ") + chalk5.dim("Quick Start:".padEnd(55)) + chalk5.green("\u2551"),
|
|
1013
|
+
chalk5.green(" \u2551 ") + chalk5.white(" buildwithnexus - Interactive shell".padEnd(55)) + chalk5.green("\u2551"),
|
|
1014
|
+
chalk5.green(" \u2551 ") + chalk5.white(" buildwithnexus brainstorm - Brainstorm ideas".padEnd(55)) + chalk5.green("\u2551"),
|
|
1015
|
+
chalk5.green(" \u2551 ") + chalk5.white(" buildwithnexus status - Check health".padEnd(55)) + chalk5.green("\u2551"),
|
|
1016
|
+
chalk5.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"),
|
|
1017
|
+
chalk5.green(" \u2551 ") + chalk5.dim("All commands:".padEnd(55)) + chalk5.green("\u2551"),
|
|
1018
|
+
chalk5.green(" \u2551 ") + chalk5.white(" buildwithnexus stop/start/update/logs/ssh/destroy".padEnd(55)) + chalk5.green("\u2551"),
|
|
1019
|
+
chalk5.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"),
|
|
1020
|
+
""
|
|
1021
|
+
);
|
|
1022
|
+
console.log(lines.join("\n"));
|
|
1023
|
+
}
|
|
1024
|
+
var init_banner = __esm({
|
|
1025
|
+
"src/ui/banner.ts"() {
|
|
1026
|
+
"use strict";
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
// src/bin.ts
|
|
1031
|
+
init_init_command();
|
|
1032
|
+
import { program } from "commander";
|
|
1033
|
+
|
|
1034
|
+
// src/cli/tui.ts
|
|
1035
|
+
import chalk from "chalk";
|
|
1036
|
+
import stringWidth from "string-width";
|
|
1037
|
+
var colors = {
|
|
1038
|
+
accent: chalk.hex("#7D56F4"),
|
|
1039
|
+
// Brand purple
|
|
1040
|
+
success: chalk.hex("#00FF87"),
|
|
1041
|
+
// Bright green
|
|
1042
|
+
warning: chalk.hex("#FFB86C"),
|
|
1043
|
+
// Amber
|
|
1044
|
+
info: chalk.hex("#8BE9FD"),
|
|
1045
|
+
// Cyan
|
|
1046
|
+
muted: chalk.gray,
|
|
1047
|
+
// Adaptive gray
|
|
1048
|
+
error: chalk.red
|
|
1049
|
+
};
|
|
1050
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1051
|
+
var STATUS_SYMBOLS = {
|
|
1052
|
+
done: colors.success("\u2714"),
|
|
1053
|
+
active: colors.info("\u25C9"),
|
|
1054
|
+
pending: colors.muted("\u25CB"),
|
|
1055
|
+
error: colors.error("\u2716")
|
|
1056
|
+
};
|
|
1057
|
+
var TUI = class {
|
|
1058
|
+
taskStartTime = 0;
|
|
1059
|
+
eventCount = 0;
|
|
1060
|
+
spinnerIndex = 0;
|
|
1061
|
+
getSpinner() {
|
|
1062
|
+
const frame = SPINNER_FRAMES[this.spinnerIndex % SPINNER_FRAMES.length];
|
|
1063
|
+
this.spinnerIndex++;
|
|
1064
|
+
return frame;
|
|
658
1065
|
}
|
|
659
|
-
|
|
660
|
-
|
|
1066
|
+
displayHeader(task, agent) {
|
|
1067
|
+
console.clear();
|
|
1068
|
+
const headerBox = this.makeRoundedBox(
|
|
1069
|
+
colors.accent("\u{1F680} NEXUS - Autonomous Agent Orchestration"),
|
|
1070
|
+
62,
|
|
1071
|
+
colors.accent
|
|
1072
|
+
);
|
|
1073
|
+
console.log(headerBox);
|
|
1074
|
+
console.log("");
|
|
1075
|
+
console.log(colors.muted("Task") + colors.muted(": ") + chalk.white(task));
|
|
1076
|
+
console.log(colors.muted("Agent") + colors.muted(": ") + colors.info(agent));
|
|
1077
|
+
console.log("");
|
|
1078
|
+
this.taskStartTime = Date.now();
|
|
661
1079
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
return "plan";
|
|
1080
|
+
displayConnecting() {
|
|
1081
|
+
console.log(`${this.getSpinner()} ${colors.warning("Connecting to backend...")}`);
|
|
665
1082
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
return wordCount > 6 ? "plan" : "build";
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// src/cli/interactive.ts
|
|
673
|
-
init_config();
|
|
674
|
-
async function interactiveMode() {
|
|
675
|
-
const backendUrl = process.env.BACKEND_URL || "http://localhost:4200";
|
|
676
|
-
console.log(chalk2.cyan("\n\u{1F527} Configure API Keys\n"));
|
|
677
|
-
const { deepAgentsInitCommand: deepAgentsInitCommand2 } = await Promise.resolve().then(() => (init_init_command(), init_command_exports));
|
|
678
|
-
await deepAgentsInitCommand2();
|
|
679
|
-
reloadEnv();
|
|
680
|
-
if (!hasAnyKey()) {
|
|
681
|
-
console.error("Error: At least one API key is required to use buildwithnexus.");
|
|
682
|
-
process.exit(1);
|
|
1083
|
+
displayConnected(runId) {
|
|
1084
|
+
console.log(`${STATUS_SYMBOLS.done} Connected \u2022 ${colors.muted(`run: ${runId}`)}`);
|
|
1085
|
+
console.log("");
|
|
683
1086
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
console.log(chalk2.gray(` OpenAI: ${keys.openai ? "\u2713" : "\u2717"}`));
|
|
688
|
-
console.log(chalk2.gray(` Google: ${keys.google ? "\u2713" : "\u2717"}
|
|
689
|
-
`));
|
|
690
|
-
try {
|
|
691
|
-
const response = await fetch(`${backendUrl}/health`);
|
|
692
|
-
if (!response.ok) {
|
|
693
|
-
console.error(chalk2.red("\u274C Backend not running. Start it with: buildwithnexus server"));
|
|
694
|
-
process.exit(1);
|
|
695
|
-
}
|
|
696
|
-
} catch {
|
|
697
|
-
console.error(chalk2.red("\u274C Cannot connect to backend at " + backendUrl));
|
|
698
|
-
process.exit(1);
|
|
1087
|
+
displayStreamStart() {
|
|
1088
|
+
console.log(chalk.bold(colors.accent("\u{1F4E1} Streaming Events")));
|
|
1089
|
+
console.log("");
|
|
699
1090
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
const task = await ask(chalk2.bold.blue("\u{1F4DD} Task: "));
|
|
710
|
-
if (task.toLowerCase() === "exit") {
|
|
711
|
-
console.log(chalk2.yellow("\nGoodbye! \u{1F44B}\n"));
|
|
712
|
-
rl.close();
|
|
713
|
-
process.exit(0);
|
|
714
|
-
}
|
|
715
|
-
if (!task.trim()) {
|
|
716
|
-
console.log(chalk2.red("Please enter a task.\n"));
|
|
717
|
-
continue;
|
|
718
|
-
}
|
|
719
|
-
const suggestedMode = classifyIntent(task).toUpperCase();
|
|
720
|
-
tui.displaySuggestedMode(suggestedMode, task);
|
|
721
|
-
const currentMode = await selectMode(suggestedMode, ask);
|
|
722
|
-
await runModeLoop(currentMode, task, backendUrl, rl, ask);
|
|
1091
|
+
displayPlan(task, steps) {
|
|
1092
|
+
console.log("");
|
|
1093
|
+
const planHeader = colors.accent("Plan Breakdown");
|
|
1094
|
+
const planLines = [];
|
|
1095
|
+
steps.forEach((step, i) => {
|
|
1096
|
+
planLines.push(` ${STATUS_SYMBOLS.pending} ${chalk.white(`Step ${i + 1}:`)} ${step}`);
|
|
1097
|
+
});
|
|
1098
|
+
const planBox = this.makeDoubleBox(planHeader, planLines.join("\n"), 62, colors.accent);
|
|
1099
|
+
console.log(planBox);
|
|
723
1100
|
console.log("");
|
|
724
1101
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
chalk2.gray("Press ") + chalk2.bold("Enter") + chalk2.gray(" to use ") + modeColor[suggested](suggested) + chalk2.gray(" or type ") + chalk2.bold("plan") + chalk2.gray("/") + chalk2.bold("build") + chalk2.gray("/") + chalk2.bold("brainstorm") + chalk2.gray(" to switch: ")
|
|
735
|
-
);
|
|
736
|
-
const answer = await ask(chalk2.gray("> "));
|
|
737
|
-
const lower = answer.trim().toLowerCase();
|
|
738
|
-
if (lower === "p" || lower === "plan") return "PLAN";
|
|
739
|
-
if (lower === "b" || lower === "build") return "BUILD";
|
|
740
|
-
if (lower === "br" || lower === "brainstorm") return "BRAINSTORM";
|
|
741
|
-
return suggested;
|
|
742
|
-
}
|
|
743
|
-
async function runModeLoop(mode, task, backendUrl, rl, ask) {
|
|
744
|
-
let currentMode = mode;
|
|
745
|
-
while (true) {
|
|
746
|
-
console.clear();
|
|
747
|
-
printAppHeader();
|
|
748
|
-
tui.displayModeBar(currentMode);
|
|
749
|
-
tui.displayModeHeader(currentMode);
|
|
750
|
-
if (currentMode === "PLAN") {
|
|
751
|
-
const next = await planModeLoop(task, backendUrl, rl, ask);
|
|
752
|
-
if (next === "BUILD") {
|
|
753
|
-
currentMode = "BUILD";
|
|
754
|
-
continue;
|
|
755
|
-
}
|
|
756
|
-
if (next === "switch") {
|
|
757
|
-
currentMode = await promptModeSwitch(currentMode, ask);
|
|
758
|
-
continue;
|
|
759
|
-
}
|
|
1102
|
+
displayEvent(type, data) {
|
|
1103
|
+
this.eventCount++;
|
|
1104
|
+
const content = data["content"] || "";
|
|
1105
|
+
if (type === "agent_working") {
|
|
1106
|
+
const agent = data["agent"] || "Agent";
|
|
1107
|
+
const agentTask = data["task"] || "";
|
|
1108
|
+
console.log("");
|
|
1109
|
+
console.log(` ${colors.info("\u{1F464}")} ${colors.info(chalk.bold(agent))}`);
|
|
1110
|
+
console.log(` ${colors.muted("\u2192")} ${agentTask}`);
|
|
760
1111
|
return;
|
|
761
1112
|
}
|
|
762
|
-
if (
|
|
763
|
-
const
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
1113
|
+
if (type === "agent_result") {
|
|
1114
|
+
const result = data["result"] || "";
|
|
1115
|
+
let displayResult = result;
|
|
1116
|
+
if (displayResult.length > 100) {
|
|
1117
|
+
displayResult = displayResult.substring(0, 97) + "...";
|
|
767
1118
|
}
|
|
1119
|
+
console.log(` ${STATUS_SYMBOLS.done} ${chalk.white(displayResult)}`);
|
|
768
1120
|
return;
|
|
769
1121
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
|
|
1122
|
+
const eventConfig = {
|
|
1123
|
+
thought: { icon: "\u{1F4AD}", color: colors.info },
|
|
1124
|
+
action: { icon: "\u26A1", color: colors.warning },
|
|
1125
|
+
observation: { icon: "\u2713", color: colors.success },
|
|
1126
|
+
started: { icon: "\u25B6", color: colors.info },
|
|
1127
|
+
done: { icon: "\u2728", color: colors.success },
|
|
1128
|
+
execution_complete: { icon: "\u2728", color: colors.success },
|
|
1129
|
+
error: { icon: "\u2716", color: colors.error }
|
|
1130
|
+
};
|
|
1131
|
+
const config = eventConfig[type] || { icon: "\u25CF", color: colors.muted };
|
|
1132
|
+
let displayContent = content;
|
|
1133
|
+
if (displayContent.length > 100) {
|
|
1134
|
+
displayContent = displayContent.substring(0, 97) + "...";
|
|
777
1135
|
}
|
|
1136
|
+
console.log(` ${config.icon} ${config.color(displayContent)}`);
|
|
778
1137
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
}
|
|
788
|
-
async function promptModeSwitch(current, ask) {
|
|
789
|
-
const others = ["PLAN", "BUILD", "BRAINSTORM"].filter((m) => m !== current);
|
|
790
|
-
console.log("");
|
|
791
|
-
console.log(
|
|
792
|
-
chalk2.gray("Switch to: ") + others.map((m, i) => chalk2.bold(`[${i + 1}] ${m}`)).join(chalk2.gray(" ")) + chalk2.gray(" [Enter to stay in ") + chalk2.bold(current) + chalk2.gray("]")
|
|
793
|
-
);
|
|
794
|
-
const answer = await ask(chalk2.gray("> "));
|
|
795
|
-
const n = parseInt(answer.trim(), 10);
|
|
796
|
-
if (n === 1) return others[0];
|
|
797
|
-
if (n === 2) return others[1];
|
|
798
|
-
return current;
|
|
799
|
-
}
|
|
800
|
-
async function planModeLoop(task, backendUrl, rl, ask) {
|
|
801
|
-
console.log(chalk2.bold("Task:"), chalk2.white(task));
|
|
802
|
-
console.log("");
|
|
803
|
-
console.log(chalk2.yellow("\u23F3 Fetching plan from backend..."));
|
|
804
|
-
let steps = [];
|
|
805
|
-
const keys = loadApiKeys();
|
|
806
|
-
try {
|
|
807
|
-
const response = await fetch(`${backendUrl}/api/run`, {
|
|
808
|
-
method: "POST",
|
|
809
|
-
headers: { "Content-Type": "application/json" },
|
|
810
|
-
body: JSON.stringify({ task, agent_role: "engineer", agent_goal: "", api_key: keys.anthropic || "", openai_api_key: keys.openai || "", google_api_key: keys.google || "" })
|
|
811
|
-
});
|
|
812
|
-
if (!response.ok) {
|
|
813
|
-
console.error(chalk2.red("Backend error \u2014 cannot fetch plan."));
|
|
814
|
-
return "cancel";
|
|
815
|
-
}
|
|
816
|
-
const { run_id } = await response.json();
|
|
817
|
-
tui.displayConnected(run_id);
|
|
818
|
-
const streamResponse = await fetch(`${backendUrl}/api/stream/${run_id}`);
|
|
819
|
-
const reader = streamResponse.body?.getReader();
|
|
820
|
-
const decoder = new TextDecoder();
|
|
821
|
-
if (!reader) throw new Error("No response body");
|
|
822
|
-
let buffer = "";
|
|
823
|
-
let planReceived = false;
|
|
824
|
-
outer: while (true) {
|
|
825
|
-
const { done, value } = await reader.read();
|
|
826
|
-
if (done) break;
|
|
827
|
-
buffer += decoder.decode(value, { stream: true });
|
|
828
|
-
const lines = buffer.split("\n");
|
|
829
|
-
buffer = lines.pop() || "";
|
|
830
|
-
for (const line of lines) {
|
|
831
|
-
if (!line.startsWith("data: ")) continue;
|
|
832
|
-
try {
|
|
833
|
-
const parsed = JSON.parse(line.slice(6));
|
|
834
|
-
if (parsed.type === "plan") {
|
|
835
|
-
steps = parsed.data["steps"] || [];
|
|
836
|
-
planReceived = true;
|
|
837
|
-
break outer;
|
|
838
|
-
} else if (parsed.type === "error") {
|
|
839
|
-
const errorMsg = parsed.data["error"] || parsed.data["content"] || "Unknown error";
|
|
840
|
-
tui.displayError(errorMsg);
|
|
841
|
-
return "cancel";
|
|
842
|
-
}
|
|
843
|
-
} catch {
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
reader.cancel();
|
|
848
|
-
if (!planReceived || steps.length === 0) {
|
|
849
|
-
console.log(chalk2.yellow("No plan received from backend."));
|
|
850
|
-
steps = ["(no steps returned \u2014 execute anyway?)"];
|
|
1138
|
+
displayResults(summary, todosCompleted) {
|
|
1139
|
+
console.log("");
|
|
1140
|
+
console.log(colors.success("\u2501".repeat(60)));
|
|
1141
|
+
console.log(colors.success.bold("\u2728 Execution Complete"));
|
|
1142
|
+
console.log("");
|
|
1143
|
+
const lines = summary.split("\n");
|
|
1144
|
+
for (const line of lines) {
|
|
1145
|
+
console.log(` ${colors.success("\u2502")} ${chalk.white(line)}`);
|
|
851
1146
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
console.
|
|
855
|
-
return "cancel";
|
|
1147
|
+
console.log("");
|
|
1148
|
+
console.log(` ${colors.muted(`${todosCompleted} subtask(s) completed`)}`);
|
|
1149
|
+
console.log("");
|
|
856
1150
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
console.log(
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
return "BUILD";
|
|
863
|
-
}
|
|
864
|
-
if (answer === "n" || answer === "\x1B") {
|
|
865
|
-
console.log(chalk2.yellow("\nExecution cancelled.\n"));
|
|
866
|
-
return "cancel";
|
|
867
|
-
}
|
|
868
|
-
if (answer === "e" || answer === "edit") {
|
|
869
|
-
steps = await editPlanSteps(steps, ask);
|
|
870
|
-
displayPlanSteps(steps);
|
|
871
|
-
continue;
|
|
872
|
-
}
|
|
873
|
-
if (answer === "s" || answer === "switch") {
|
|
874
|
-
return "switch";
|
|
875
|
-
}
|
|
1151
|
+
displayError(error) {
|
|
1152
|
+
console.log("");
|
|
1153
|
+
console.log(colors.error("\u274C Error"));
|
|
1154
|
+
console.log(colors.error(error));
|
|
1155
|
+
console.log("");
|
|
876
1156
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
const
|
|
886
|
-
|
|
887
|
-
const
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
}
|
|
894
|
-
async function editPlanSteps(steps, ask) {
|
|
895
|
-
console.log(chalk2.gray("Enter step number to edit, or press Enter to finish editing:"));
|
|
896
|
-
const numStr = await ask(chalk2.bold("Step #: "));
|
|
897
|
-
const n = parseInt(numStr.trim(), 10);
|
|
898
|
-
if (!isNaN(n) && n >= 1 && n <= steps.length) {
|
|
899
|
-
console.log(chalk2.gray(`Current: ${steps[n - 1]}`));
|
|
900
|
-
const updated = await ask(chalk2.bold("New text: "));
|
|
901
|
-
if (updated.trim()) steps[n - 1] = updated.trim();
|
|
1157
|
+
displayComplete(duration) {
|
|
1158
|
+
const seconds = (duration / 1e3).toFixed(1);
|
|
1159
|
+
console.log("");
|
|
1160
|
+
console.log(colors.success.bold(`\u2728 Complete in ${seconds}s`));
|
|
1161
|
+
console.log(colors.muted(`${this.eventCount} event(s) streamed`));
|
|
1162
|
+
console.log("");
|
|
1163
|
+
}
|
|
1164
|
+
displayBox(title, content) {
|
|
1165
|
+
const box = this.makeRoundedBox(title, 60, colors.accent);
|
|
1166
|
+
console.log(box);
|
|
1167
|
+
const lines = content.split("\n");
|
|
1168
|
+
for (const line of lines) {
|
|
1169
|
+
const padded = line.substring(0, 56).padEnd(56);
|
|
1170
|
+
console.log(` ${padded}`);
|
|
1171
|
+
}
|
|
1172
|
+
console.log("");
|
|
902
1173
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1174
|
+
getElapsedTime() {
|
|
1175
|
+
return Date.now() - this.taskStartTime;
|
|
1176
|
+
}
|
|
1177
|
+
displayModeBar(current) {
|
|
1178
|
+
const modes = ["PLAN", "BUILD", "BRAINSTORM"];
|
|
1179
|
+
const modeConfig = {
|
|
1180
|
+
PLAN: { icon: "\u{1F4CB}", color: colors.info },
|
|
1181
|
+
BUILD: { icon: "\u2699\uFE0F", color: colors.success },
|
|
1182
|
+
BRAINSTORM: { icon: "\u{1F4A1}", color: chalk.blue }
|
|
1183
|
+
};
|
|
1184
|
+
const parts = modes.map((m) => {
|
|
1185
|
+
const config = modeConfig[m];
|
|
1186
|
+
const label = `${config.icon} ${m}`;
|
|
1187
|
+
if (m === current) {
|
|
1188
|
+
return config.color.bold(label);
|
|
1189
|
+
}
|
|
1190
|
+
return colors.muted(label);
|
|
914
1191
|
});
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
if (!line.startsWith("data: ")) continue;
|
|
936
|
-
try {
|
|
937
|
-
const parsed = JSON.parse(line.slice(6));
|
|
938
|
-
const type = parsed.type;
|
|
939
|
-
if (type === "execution_complete") {
|
|
940
|
-
const summary = parsed.data["summary"] || "";
|
|
941
|
-
const count = parsed.data["todos_completed"] || 0;
|
|
942
|
-
tui.displayResults(summary, count);
|
|
943
|
-
tui.displayComplete(tui.getElapsedTime());
|
|
944
|
-
break;
|
|
945
|
-
} else if (type === "done") {
|
|
946
|
-
tui.displayEvent(type, { content: "Task completed successfully" });
|
|
947
|
-
tui.displayComplete(tui.getElapsedTime());
|
|
948
|
-
break;
|
|
949
|
-
} else if (type === "error") {
|
|
950
|
-
const errorMsg = parsed.data["error"] || parsed.data["content"] || "Unknown error";
|
|
951
|
-
tui.displayError(errorMsg);
|
|
952
|
-
break;
|
|
953
|
-
} else if (type !== "plan") {
|
|
954
|
-
tui.displayEvent(type, parsed.data);
|
|
955
|
-
}
|
|
956
|
-
} catch {
|
|
957
|
-
}
|
|
1192
|
+
console.log(parts.join(colors.muted(" \u2022 ")));
|
|
1193
|
+
console.log(colors.muted("[s] switch mode"));
|
|
1194
|
+
console.log("");
|
|
1195
|
+
}
|
|
1196
|
+
displayModeHeader(mode) {
|
|
1197
|
+
const config = {
|
|
1198
|
+
PLAN: {
|
|
1199
|
+
icon: "\u{1F4CB}",
|
|
1200
|
+
desc: "Break down and review steps",
|
|
1201
|
+
color: colors.info
|
|
1202
|
+
},
|
|
1203
|
+
BUILD: {
|
|
1204
|
+
icon: "\u2699\uFE0F",
|
|
1205
|
+
desc: "Execute with live streaming",
|
|
1206
|
+
color: colors.success
|
|
1207
|
+
},
|
|
1208
|
+
BRAINSTORM: {
|
|
1209
|
+
icon: "\u{1F4A1}",
|
|
1210
|
+
desc: "Free-form Q&A and exploration",
|
|
1211
|
+
color: chalk.blue
|
|
958
1212
|
}
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
console.
|
|
1213
|
+
};
|
|
1214
|
+
const c = config[mode];
|
|
1215
|
+
console.log("");
|
|
1216
|
+
console.log(c.color.bold(`${c.icon} ${mode}`));
|
|
1217
|
+
console.log(colors.muted(c.desc));
|
|
1218
|
+
console.log("");
|
|
963
1219
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
console.log(
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
task: currentQuestion,
|
|
985
|
-
agent_role: "brainstorm",
|
|
986
|
-
agent_goal: "Generate ideas, considerations, and suggestions. Be concise and helpful.",
|
|
987
|
-
api_key: keys.anthropic || "",
|
|
988
|
-
openai_api_key: keys.openai || "",
|
|
989
|
-
google_api_key: keys.google || ""
|
|
990
|
-
})
|
|
991
|
-
});
|
|
992
|
-
if (response.ok) {
|
|
993
|
-
const { run_id } = await response.json();
|
|
994
|
-
const streamResponse = await fetch(`${backendUrl}/api/stream/${run_id}`);
|
|
995
|
-
const reader = streamResponse.body?.getReader();
|
|
996
|
-
const decoder = new TextDecoder();
|
|
997
|
-
if (reader) {
|
|
998
|
-
let buffer = "";
|
|
999
|
-
let responseText = "";
|
|
1000
|
-
outer: while (true) {
|
|
1001
|
-
const { done, value } = await reader.read();
|
|
1002
|
-
if (done) break;
|
|
1003
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1004
|
-
const lines = buffer.split("\n");
|
|
1005
|
-
buffer = lines.pop() || "";
|
|
1006
|
-
for (const line of lines) {
|
|
1007
|
-
if (!line.startsWith("data: ")) continue;
|
|
1008
|
-
try {
|
|
1009
|
-
const parsed = JSON.parse(line.slice(6));
|
|
1010
|
-
const type = parsed.type;
|
|
1011
|
-
const data = parsed.data;
|
|
1012
|
-
if (type === "done" || type === "execution_complete") {
|
|
1013
|
-
const summary = data["summary"] || "";
|
|
1014
|
-
if (summary) responseText = summary;
|
|
1015
|
-
break outer;
|
|
1016
|
-
} else if (type === "error") {
|
|
1017
|
-
const errorMsg = data["error"] || data["content"] || "Unknown error";
|
|
1018
|
-
responseText += errorMsg + "\n";
|
|
1019
|
-
break outer;
|
|
1020
|
-
} else if (type === "thought" || type === "observation") {
|
|
1021
|
-
const content = data["content"] || "";
|
|
1022
|
-
if (content) responseText += content + "\n";
|
|
1023
|
-
} else if (type === "agent_response" || type === "agent_result") {
|
|
1024
|
-
const content = data["content"] || data["result"] || "";
|
|
1025
|
-
if (content) responseText += content + "\n";
|
|
1026
|
-
} else if (type === "action") {
|
|
1027
|
-
const content = data["content"] || "";
|
|
1028
|
-
if (content) responseText += content + "\n";
|
|
1029
|
-
} else if (type === "agent_working") {
|
|
1030
|
-
} else if (type !== "plan") {
|
|
1031
|
-
const content = data["content"] || data["response"] || "";
|
|
1032
|
-
if (content) responseText += content + "\n";
|
|
1033
|
-
}
|
|
1034
|
-
} catch {
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
reader.cancel();
|
|
1039
|
-
if (responseText.trim()) {
|
|
1040
|
-
tui.displayBrainstormResponse(responseText.trim());
|
|
1041
|
-
} else {
|
|
1042
|
-
console.log(chalk2.gray("(No response received from agent)"));
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1220
|
+
displaySuggestedMode(mode, task) {
|
|
1221
|
+
const modeColor = {
|
|
1222
|
+
PLAN: colors.info,
|
|
1223
|
+
BUILD: colors.success,
|
|
1224
|
+
BRAINSTORM: chalk.blue
|
|
1225
|
+
};
|
|
1226
|
+
const taskPreview = task.length > 45 ? task.substring(0, 42) + "..." : task;
|
|
1227
|
+
console.log("");
|
|
1228
|
+
console.log(
|
|
1229
|
+
colors.muted("Suggested: ") + modeColor[mode].bold(mode) + colors.muted(` for "${taskPreview}"`)
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
displayBrainstormResponse(response) {
|
|
1233
|
+
console.log("");
|
|
1234
|
+
console.log(chalk.blue.bold("\u{1F4A1} Ideas & Analysis"));
|
|
1235
|
+
console.log("");
|
|
1236
|
+
const lines = response.split("\n");
|
|
1237
|
+
for (const line of lines) {
|
|
1238
|
+
if (line.trim()) {
|
|
1239
|
+
console.log(` ${chalk.white(line)}`);
|
|
1045
1240
|
} else {
|
|
1046
|
-
console.log(
|
|
1241
|
+
console.log("");
|
|
1047
1242
|
}
|
|
1048
|
-
} catch (err) {
|
|
1049
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1050
|
-
console.error(chalk2.red("Error: " + msg));
|
|
1051
1243
|
}
|
|
1052
|
-
|
|
1053
|
-
const lower = followUp.trim().toLowerCase();
|
|
1054
|
-
if (lower === "done" || lower === "exit") return "done";
|
|
1055
|
-
if (lower === "switch") return "switch";
|
|
1056
|
-
if (!followUp.trim()) continue;
|
|
1057
|
-
currentQuestion = followUp.trim();
|
|
1244
|
+
console.log("");
|
|
1058
1245
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
// src/cli.ts
|
|
1062
|
-
import { Command as Command15 } from "commander";
|
|
1063
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
1064
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
1065
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1066
|
-
|
|
1067
|
-
// src/commands/install.ts
|
|
1068
|
-
import { Command } from "commander";
|
|
1069
|
-
import { execa as execa2 } from "execa";
|
|
1070
|
-
|
|
1071
|
-
// src/ui/spinner.ts
|
|
1072
|
-
import ora from "ora";
|
|
1073
|
-
import chalk3 from "chalk";
|
|
1074
|
-
function createSpinner(text) {
|
|
1075
|
-
return ora({ text, color: "cyan", spinner: "dots" });
|
|
1076
|
-
}
|
|
1077
|
-
function succeed(spinner, text) {
|
|
1078
|
-
spinner.succeed(chalk3.green(text));
|
|
1079
|
-
}
|
|
1080
|
-
function fail(spinner, text) {
|
|
1081
|
-
spinner.fail(chalk3.red(text));
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
// src/ui/logger.ts
|
|
1085
|
-
import chalk4 from "chalk";
|
|
1086
|
-
var log = {
|
|
1087
|
-
step(msg) {
|
|
1088
|
-
console.log(chalk4.cyan(" \u2192 ") + msg);
|
|
1089
|
-
},
|
|
1090
|
-
success(msg) {
|
|
1091
|
-
console.log(chalk4.green(" \u2713 ") + msg);
|
|
1092
|
-
},
|
|
1093
|
-
error(msg) {
|
|
1094
|
-
console.error(chalk4.red(" \u2717 ") + msg);
|
|
1095
|
-
},
|
|
1096
|
-
warn(msg) {
|
|
1097
|
-
console.log(chalk4.yellow(" \u26A0 ") + msg);
|
|
1098
|
-
},
|
|
1099
|
-
dim(msg) {
|
|
1100
|
-
console.log(chalk4.dim(" " + msg));
|
|
1101
|
-
},
|
|
1102
|
-
detail(label, value) {
|
|
1103
|
-
console.log(chalk4.dim(" " + label + ": ") + value);
|
|
1104
|
-
},
|
|
1105
|
-
progress(current, total, label) {
|
|
1106
|
-
const pct = Math.round(current / total * 100);
|
|
1107
|
-
const filled = Math.round(current / total * 20);
|
|
1108
|
-
const bar = chalk4.cyan("\u2588".repeat(filled)) + chalk4.dim("\u2591".repeat(20 - filled));
|
|
1109
|
-
process.stdout.write(`\r [${bar}] ${chalk4.bold(`${pct}%`)} ${chalk4.dim(label)}`);
|
|
1110
|
-
if (current >= total) process.stdout.write("\n");
|
|
1246
|
+
displayPermissionPrompt(message) {
|
|
1247
|
+
return colors.accent.bold(message) + colors.muted(" (y/n) ");
|
|
1111
1248
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
function detectPlatform() {
|
|
1117
|
-
const platform = os3.platform();
|
|
1118
|
-
const arch = os3.arch();
|
|
1119
|
-
if (platform === "darwin") {
|
|
1120
|
-
return {
|
|
1121
|
-
os: "mac",
|
|
1122
|
-
arch: arch === "arm64" ? "arm64" : "x64",
|
|
1123
|
-
dockerPlatform: arch === "arm64" ? "linux/arm64" : "linux/amd64"
|
|
1124
|
-
};
|
|
1249
|
+
padToWidth(text, targetWidth) {
|
|
1250
|
+
const visibleWidth = stringWidth(text);
|
|
1251
|
+
const padding = Math.max(0, targetWidth - visibleWidth);
|
|
1252
|
+
return text + " ".repeat(padding);
|
|
1125
1253
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1254
|
+
truncateToWidth(text, maxWidth) {
|
|
1255
|
+
let result = "";
|
|
1256
|
+
let width = 0;
|
|
1257
|
+
for (const char of text) {
|
|
1258
|
+
const charWidth = stringWidth(char);
|
|
1259
|
+
if (width + charWidth > maxWidth) break;
|
|
1260
|
+
result += char;
|
|
1261
|
+
width += charWidth;
|
|
1262
|
+
}
|
|
1263
|
+
return result;
|
|
1132
1264
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1265
|
+
makeRoundedBox(title, width, borderColor) {
|
|
1266
|
+
const lines = [];
|
|
1267
|
+
const innerWidth = width - 4;
|
|
1268
|
+
const titleText = ` ${title} `;
|
|
1269
|
+
const titleTruncated = this.truncateToWidth(titleText, innerWidth);
|
|
1270
|
+
const titlePadded = this.padToWidth(titleTruncated, innerWidth);
|
|
1271
|
+
lines.push(borderColor("\u256D" + "\u2500".repeat(innerWidth) + "\u256E"));
|
|
1272
|
+
lines.push(borderColor("\u2502") + chalk.bold(titlePadded) + borderColor("\u2502"));
|
|
1273
|
+
lines.push(borderColor("\u2570" + "\u2500".repeat(innerWidth) + "\u256F"));
|
|
1274
|
+
return lines.join("\n");
|
|
1139
1275
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1276
|
+
makeDoubleBox(title, content, width, borderColor) {
|
|
1277
|
+
const lines = [];
|
|
1278
|
+
const innerWidth = width - 4;
|
|
1279
|
+
const titleText = ` ${title} `;
|
|
1280
|
+
const titleTruncated = this.truncateToWidth(titleText, innerWidth);
|
|
1281
|
+
const titlePadded = this.padToWidth(titleTruncated, innerWidth);
|
|
1282
|
+
lines.push(borderColor("\u2554" + "\u2550".repeat(innerWidth) + "\u2557"));
|
|
1283
|
+
lines.push(borderColor("\u2551") + chalk.bold(titlePadded) + borderColor("\u2551"));
|
|
1284
|
+
lines.push(borderColor("\u2560" + "\u2550".repeat(innerWidth) + "\u2563"));
|
|
1285
|
+
const contentLines = content.split("\n");
|
|
1286
|
+
for (const line of contentLines) {
|
|
1287
|
+
const contentWidth = innerWidth - 2;
|
|
1288
|
+
const truncated = this.truncateToWidth(line, contentWidth);
|
|
1289
|
+
const padded = this.padToWidth(truncated, contentWidth);
|
|
1290
|
+
lines.push(borderColor("\u2551") + " " + padded + borderColor("\u2551"));
|
|
1291
|
+
}
|
|
1292
|
+
lines.push(borderColor("\u255A" + "\u2550".repeat(innerWidth) + "\u255D"));
|
|
1293
|
+
return lines.join("\n");
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
var tui = new TUI();
|
|
1142
1297
|
|
|
1143
|
-
// src/core/
|
|
1144
|
-
import
|
|
1145
|
-
import
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1298
|
+
// src/core/config.ts
|
|
1299
|
+
import dotenv from "dotenv";
|
|
1300
|
+
import path3 from "path";
|
|
1301
|
+
import os from "os";
|
|
1302
|
+
function loadApiKeys() {
|
|
1303
|
+
const keyNames = [
|
|
1304
|
+
["ANTHROPIC_API_KEY", "anthropic"],
|
|
1305
|
+
["OPENAI_API_KEY", "openai"],
|
|
1306
|
+
["GOOGLE_API_KEY", "google"]
|
|
1307
|
+
];
|
|
1308
|
+
const result = {};
|
|
1309
|
+
for (const [envName, key] of keyNames) {
|
|
1310
|
+
const raw = process.env[envName];
|
|
1311
|
+
if (raw !== void 0) {
|
|
1312
|
+
if (raw.trim() === "") {
|
|
1313
|
+
console.warn(`WARNING: ${envName} is set but empty - it will be treated as unconfigured`);
|
|
1314
|
+
result[key] = void 0;
|
|
1315
|
+
} else {
|
|
1316
|
+
result[key] = raw;
|
|
1317
|
+
}
|
|
1318
|
+
} else {
|
|
1319
|
+
result[key] = void 0;
|
|
1320
|
+
}
|
|
1154
1321
|
}
|
|
1322
|
+
return result;
|
|
1155
1323
|
}
|
|
1156
|
-
|
|
1324
|
+
function hasAnyKey() {
|
|
1325
|
+
return !!(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.GOOGLE_API_KEY);
|
|
1326
|
+
}
|
|
1327
|
+
function validateBackendUrl(url) {
|
|
1328
|
+
let parsed;
|
|
1157
1329
|
try {
|
|
1158
|
-
|
|
1159
|
-
return true;
|
|
1330
|
+
parsed = new URL(url);
|
|
1160
1331
|
} catch {
|
|
1161
|
-
return false;
|
|
1332
|
+
return { valid: false, error: `Invalid backend URL: ${url}` };
|
|
1162
1333
|
}
|
|
1334
|
+
const hostname = parsed.hostname;
|
|
1335
|
+
const isLocal = hostname === "localhost" || hostname === "127.0.0.1";
|
|
1336
|
+
if (parsed.protocol === "https:") {
|
|
1337
|
+
return { valid: true };
|
|
1338
|
+
}
|
|
1339
|
+
if (isLocal) {
|
|
1340
|
+
return { valid: true };
|
|
1341
|
+
}
|
|
1342
|
+
return {
|
|
1343
|
+
valid: false,
|
|
1344
|
+
error: "WARNING: Backend URL is not localhost and not HTTPS. API keys will be transmitted in plaintext. Use SSH tunnel or HTTPS endpoint."
|
|
1345
|
+
};
|
|
1163
1346
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1347
|
+
|
|
1348
|
+
// src/core/api.ts
|
|
1349
|
+
function buildRunPayload(task, agentRole, agentGoal, keys) {
|
|
1350
|
+
const k = keys ?? loadApiKeys();
|
|
1351
|
+
return {
|
|
1352
|
+
task,
|
|
1353
|
+
agent_role: agentRole,
|
|
1354
|
+
agent_goal: agentGoal,
|
|
1355
|
+
api_key: k.anthropic || "",
|
|
1356
|
+
openai_api_key: k.openai || "",
|
|
1357
|
+
google_api_key: k.google || ""
|
|
1358
|
+
};
|
|
1166
1359
|
}
|
|
1167
|
-
async function
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
], {
|
|
1180
|
-
stdio: "inherit",
|
|
1181
|
-
env: { ...process.env, NONINTERACTIVE: "1" }
|
|
1182
|
-
});
|
|
1360
|
+
async function httpPost(httpPort, path11, body, timeoutMs = 6e4) {
|
|
1361
|
+
const res = await fetch(`http://localhost:${httpPort}${path11}`, {
|
|
1362
|
+
method: "POST",
|
|
1363
|
+
headers: { "Content-Type": "application/json" },
|
|
1364
|
+
body: JSON.stringify(body),
|
|
1365
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
1366
|
+
});
|
|
1367
|
+
if (!res.ok) throw new Error(`Server returned ${res.status}`);
|
|
1368
|
+
const text = await res.text();
|
|
1369
|
+
try {
|
|
1370
|
+
const parsed = JSON.parse(text);
|
|
1371
|
+
return parsed.response ?? parsed.message ?? text;
|
|
1183
1372
|
} catch {
|
|
1184
|
-
|
|
1185
|
-
'Failed to install Homebrew automatically.\n\n Install Homebrew manually:\n /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"\n\n Then re-run:\n buildwithnexus init'
|
|
1186
|
-
);
|
|
1373
|
+
return text;
|
|
1187
1374
|
}
|
|
1375
|
+
}
|
|
1376
|
+
async function httpGet(httpPort, path11, timeoutMs = 1e4) {
|
|
1188
1377
|
try {
|
|
1189
|
-
await
|
|
1378
|
+
const res = await fetch(`http://localhost:${httpPort}${path11}`, {
|
|
1379
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
1380
|
+
});
|
|
1381
|
+
const text = await res.text();
|
|
1382
|
+
return { ok: res.ok, text };
|
|
1190
1383
|
} catch {
|
|
1191
|
-
|
|
1192
|
-
const intelPath = "/usr/local/bin";
|
|
1193
|
-
process.env.PATH = `${armPath}:${intelPath}:${process.env.PATH}`;
|
|
1194
|
-
log.dim("Added Homebrew paths to PATH for this session.");
|
|
1384
|
+
return { ok: false, text: "" };
|
|
1195
1385
|
}
|
|
1386
|
+
}
|
|
1387
|
+
async function checkServerHealth(target, timeoutMs = 1e4) {
|
|
1388
|
+
const url = typeof target === "number" ? `http://localhost:${target}/health` : `${target}/health`;
|
|
1196
1389
|
try {
|
|
1197
|
-
await
|
|
1198
|
-
|
|
1390
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
1391
|
+
return res.ok;
|
|
1199
1392
|
} catch {
|
|
1200
|
-
|
|
1201
|
-
"Homebrew was installed but is not available on PATH.\n\n Try opening a new terminal and re-running:\n buildwithnexus init"
|
|
1202
|
-
);
|
|
1393
|
+
return false;
|
|
1203
1394
|
}
|
|
1204
1395
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
await execa("open", [DOCKER_DESKTOP_APP_PATH]);
|
|
1219
|
-
launched = true;
|
|
1220
|
-
log.dim(`Launch command succeeded via ${DOCKER_DESKTOP_APP_PATH}`);
|
|
1221
|
-
} catch {
|
|
1222
|
-
log.warn(`Could not launch via ${DOCKER_DESKTOP_APP_PATH} \u2014 trying fallback...`);
|
|
1223
|
-
}
|
|
1224
|
-
if (!launched) {
|
|
1225
|
-
log.dim("Trying: open -a Docker");
|
|
1226
|
-
try {
|
|
1227
|
-
await execa("open", ["-a", "Docker"]);
|
|
1228
|
-
launched = true;
|
|
1229
|
-
log.dim("Launch command succeeded via open -a Docker");
|
|
1230
|
-
} catch {
|
|
1231
|
-
log.warn("Both launch attempts failed.");
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
if (launched) {
|
|
1235
|
-
log.step("Docker Desktop is starting up. Waiting for the daemon to be ready (up to 120s)...");
|
|
1236
|
-
try {
|
|
1237
|
-
await waitForDockerDaemon(12e4);
|
|
1238
|
-
return;
|
|
1239
|
-
} catch {
|
|
1240
|
-
log.warn("Docker Desktop was launched but the daemon did not become ready in time.");
|
|
1241
|
-
}
|
|
1242
|
-
} else {
|
|
1243
|
-
log.warn("Could not launch Docker Desktop. Will fall back to reinstalling via Homebrew.");
|
|
1244
|
-
}
|
|
1245
|
-
} else {
|
|
1246
|
-
log.step(`Docker Desktop not found at ${DOCKER_DESKTOP_APP_PATH}.`);
|
|
1247
|
-
}
|
|
1248
|
-
log.step("Installing Docker Desktop via Homebrew...");
|
|
1249
|
-
await ensureHomebrew();
|
|
1250
|
-
try {
|
|
1251
|
-
await execa("brew", ["install", "--cask", "docker"], {
|
|
1252
|
-
stdio: "inherit",
|
|
1253
|
-
timeout: 6e4
|
|
1254
|
-
});
|
|
1255
|
-
} catch (err) {
|
|
1256
|
-
const e = err;
|
|
1257
|
-
if (e.killed && e.signal === "SIGINT") {
|
|
1258
|
-
throw new Error("Docker installation cancelled by user (Ctrl+C)");
|
|
1259
|
-
}
|
|
1260
|
-
if (e.timedOut) {
|
|
1261
|
-
throw new Error(
|
|
1262
|
-
"Docker installation via Homebrew timed out after 60 seconds.\n\n The password prompt may be waiting for input. Try installing manually:\n brew install --cask docker\n\n After installing, re-run:\n buildwithnexus init"
|
|
1263
|
-
);
|
|
1264
|
-
}
|
|
1265
|
-
throw new Error(
|
|
1266
|
-
"Failed to install Docker via Homebrew.\n\n Try installing Docker Desktop manually:\n https://www.docker.com/products/docker-desktop\n\n After installing, re-run:\n buildwithnexus init"
|
|
1267
|
-
);
|
|
1268
|
-
}
|
|
1269
|
-
log.step("Launching Docker Desktop...");
|
|
1270
|
-
let postInstallLaunched = false;
|
|
1271
|
-
log.dim(`Trying: open ${DOCKER_DESKTOP_APP_PATH}`);
|
|
1396
|
+
|
|
1397
|
+
// src/core/sse-parser.ts
|
|
1398
|
+
async function* parseSSEStream(reader) {
|
|
1399
|
+
const decoder = new TextDecoder();
|
|
1400
|
+
let buffer = "";
|
|
1401
|
+
while (true) {
|
|
1402
|
+
const { done, value } = await reader.read();
|
|
1403
|
+
if (done) break;
|
|
1404
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1405
|
+
const lines = buffer.split("\n");
|
|
1406
|
+
buffer = lines.pop() || "";
|
|
1407
|
+
for (const line of lines) {
|
|
1408
|
+
if (!line.startsWith("data: ")) continue;
|
|
1272
1409
|
try {
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
log.warn(`Could not launch via ${DOCKER_DESKTOP_APP_PATH} \u2014 trying fallback...`);
|
|
1278
|
-
}
|
|
1279
|
-
if (!postInstallLaunched) {
|
|
1280
|
-
log.dim("Trying: open -a Docker");
|
|
1281
|
-
try {
|
|
1282
|
-
await execa("open", ["-a", "Docker"]);
|
|
1283
|
-
postInstallLaunched = true;
|
|
1284
|
-
log.dim("Launch command succeeded via open -a Docker");
|
|
1285
|
-
} catch {
|
|
1286
|
-
log.warn("Both launch attempts failed after install.");
|
|
1287
|
-
}
|
|
1410
|
+
const parsed = JSON.parse(line.slice(6));
|
|
1411
|
+
yield parsed;
|
|
1412
|
+
} catch (e) {
|
|
1413
|
+
if (process.env.LOG_LEVEL === "debug") console.error("SSE parse error:", e);
|
|
1288
1414
|
}
|
|
1289
|
-
if (!postInstallLaunched) {
|
|
1290
|
-
throw new Error(
|
|
1291
|
-
"Docker Desktop was installed but could not be started automatically.\n\n Next steps:\n 1. Open Docker Desktop manually from your Applications folder\n 2. Wait for the whale icon to appear in the menu bar\n 3. Re-run: buildwithnexus init"
|
|
1292
|
-
);
|
|
1293
|
-
}
|
|
1294
|
-
log.step("Docker Desktop is starting up. Waiting for the daemon to be ready (up to 120s)...");
|
|
1295
|
-
await waitForDockerDaemon(12e4);
|
|
1296
|
-
break;
|
|
1297
1415
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
return;
|
|
1319
|
-
}
|
|
1320
|
-
log.step("Installing Docker...");
|
|
1321
|
-
log.warn("This may require your sudo password.");
|
|
1322
|
-
log.dim("Running official Docker install script from https://get.docker.com ...");
|
|
1323
|
-
try {
|
|
1324
|
-
const { stdout: script } = await execa("curl", ["-fsSL", "https://get.docker.com"]);
|
|
1325
|
-
await execa("sudo", ["sh", "-c", script], { stdio: "inherit" });
|
|
1326
|
-
log.success("Docker installed successfully.");
|
|
1327
|
-
} catch {
|
|
1328
|
-
throw new Error(
|
|
1329
|
-
"Failed to install Docker on Linux.\n\n Try installing manually:\n curl -fsSL https://get.docker.com | sudo sh\n\n After installing, re-run:\n buildwithnexus init"
|
|
1330
|
-
);
|
|
1331
|
-
}
|
|
1332
|
-
log.dim("Adding current user to docker group...");
|
|
1333
|
-
try {
|
|
1334
|
-
const user = (await execa("whoami")).stdout.trim();
|
|
1335
|
-
await execa("sudo", ["usermod", "-aG", "docker", user]);
|
|
1336
|
-
log.dim(`Added user '${user}' to docker group (may require re-login for effect).`);
|
|
1337
|
-
} catch {
|
|
1338
|
-
log.warn("Could not add user to docker group. You may need sudo for docker commands.");
|
|
1339
|
-
}
|
|
1340
|
-
log.step("Starting Docker daemon...");
|
|
1341
|
-
try {
|
|
1342
|
-
await execa("sudo", ["systemctl", "start", "docker"], { stdio: "inherit" });
|
|
1343
|
-
log.dim("Started Docker daemon via systemctl.");
|
|
1344
|
-
} catch {
|
|
1345
|
-
try {
|
|
1346
|
-
await execa("sudo", ["service", "docker", "start"], { stdio: "inherit" });
|
|
1347
|
-
log.dim("Started Docker daemon via service command.");
|
|
1348
|
-
} catch {
|
|
1349
|
-
throw new Error(
|
|
1350
|
-
"Docker was installed but the daemon could not be started.\n\n Try starting it manually:\n sudo systemctl start docker\n\n Then re-run:\n buildwithnexus init"
|
|
1351
|
-
);
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
log.step("Waiting for Docker...");
|
|
1355
|
-
await waitForDockerDaemon(3e4);
|
|
1356
|
-
break;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// src/cli/run-command.ts
|
|
1420
|
+
async function runCommand(task, options) {
|
|
1421
|
+
const backendUrl = process.env.BACKEND_URL || "http://localhost:4200";
|
|
1422
|
+
const urlCheck = validateBackendUrl(backendUrl);
|
|
1423
|
+
if (!urlCheck.valid) {
|
|
1424
|
+
console.error(`
|
|
1425
|
+
${urlCheck.error}`);
|
|
1426
|
+
process.exit(1);
|
|
1427
|
+
}
|
|
1428
|
+
tui.displayHeader(task, options.agent);
|
|
1429
|
+
tui.displayConnecting();
|
|
1430
|
+
try {
|
|
1431
|
+
if (!await checkServerHealth(backendUrl)) {
|
|
1432
|
+
console.error(
|
|
1433
|
+
"Backend not responding. Start it with:\n buildwithnexus server"
|
|
1434
|
+
);
|
|
1435
|
+
process.exit(1);
|
|
1357
1436
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
}
|
|
1379
|
-
log.step("Installing Docker Desktop...");
|
|
1380
|
-
let installed = false;
|
|
1381
|
-
try {
|
|
1382
|
-
await execa("choco", ["--version"]);
|
|
1383
|
-
log.dim("Chocolatey detected. Installing Docker Desktop via choco...");
|
|
1384
|
-
try {
|
|
1385
|
-
await execa("choco", ["install", "docker-desktop", "-y"], { stdio: "inherit" });
|
|
1386
|
-
installed = true;
|
|
1387
|
-
log.success("Docker Desktop installed via Chocolatey.");
|
|
1388
|
-
} catch {
|
|
1389
|
-
log.warn("Chocolatey install failed. Falling back to direct download...");
|
|
1390
|
-
}
|
|
1391
|
-
} catch {
|
|
1392
|
-
log.dim("Chocolatey not found. Using direct download...");
|
|
1393
|
-
}
|
|
1394
|
-
if (!installed) {
|
|
1395
|
-
log.dim("Downloading Docker Desktop installer from docker.com...");
|
|
1396
|
-
try {
|
|
1397
|
-
await execa("powershell", [
|
|
1398
|
-
"-Command",
|
|
1399
|
-
"Invoke-WebRequest -Uri 'https://desktop.docker.com/win/main/amd64/Docker Desktop Installer.exe' -OutFile '$env:TEMP\\DockerInstaller.exe'; & '$env:TEMP\\DockerInstaller.exe' Install --quiet; Remove-Item '$env:TEMP\\DockerInstaller.exe' -Force -ErrorAction SilentlyContinue"
|
|
1400
|
-
], { stdio: "inherit" });
|
|
1401
|
-
installed = true;
|
|
1402
|
-
log.success("Docker Desktop installed via direct download.");
|
|
1403
|
-
} catch {
|
|
1404
|
-
throw new Error(
|
|
1405
|
-
"Failed to install Docker Desktop on Windows.\n\n Please install Docker Desktop manually:\n 1. Download from https://www.docker.com/products/docker-desktop\n 2. Run the installer and follow the prompts\n 3. Start Docker Desktop\n 4. Re-run: buildwithnexus init"
|
|
1406
|
-
);
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
log.step("Launching Docker...");
|
|
1410
|
-
try {
|
|
1411
|
-
await execa("powershell", ["-Command", "Start-Process 'Docker Desktop'"], { stdio: "inherit" });
|
|
1412
|
-
log.dim("Docker Desktop launch command sent.");
|
|
1413
|
-
} catch {
|
|
1414
|
-
log.warn("Could not launch Docker Desktop automatically after install.");
|
|
1437
|
+
const payload = buildRunPayload(task, options.agent, options.goal || "");
|
|
1438
|
+
const response = await fetch(`${backendUrl}/api/run`, {
|
|
1439
|
+
method: "POST",
|
|
1440
|
+
headers: { "Content-Type": "application/json" },
|
|
1441
|
+
body: JSON.stringify(payload)
|
|
1442
|
+
});
|
|
1443
|
+
if (!response.ok) {
|
|
1444
|
+
console.error("Backend error");
|
|
1445
|
+
console.error(await response.text());
|
|
1446
|
+
process.exit(1);
|
|
1447
|
+
}
|
|
1448
|
+
const { run_id } = await response.json();
|
|
1449
|
+
tui.displayConnected(run_id);
|
|
1450
|
+
tui.displayStreamStart();
|
|
1451
|
+
const eventSourceUrl = `${backendUrl}/api/stream/${run_id}`;
|
|
1452
|
+
try {
|
|
1453
|
+
const response2 = await fetch(eventSourceUrl);
|
|
1454
|
+
const reader = response2.body?.getReader();
|
|
1455
|
+
if (!reader) {
|
|
1456
|
+
throw new Error("No response body");
|
|
1415
1457
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1458
|
+
for await (const parsed of parseSSEStream(reader)) {
|
|
1459
|
+
const type = parsed.type;
|
|
1460
|
+
const eventContent = parsed.data["content"] || "";
|
|
1461
|
+
if (type === "done") {
|
|
1462
|
+
tui.displayEvent(type, { content: "Task completed successfully" });
|
|
1463
|
+
tui.displayComplete(tui.getElapsedTime());
|
|
1464
|
+
process.exit(0);
|
|
1465
|
+
} else if (type === "error") {
|
|
1466
|
+
tui.displayError(eventContent);
|
|
1467
|
+
process.exit(1);
|
|
1468
|
+
} else {
|
|
1469
|
+
tui.displayEvent(type, { content: eventContent });
|
|
1470
|
+
}
|
|
1423
1471
|
}
|
|
1424
|
-
|
|
1472
|
+
} catch (error) {
|
|
1473
|
+
console.error(
|
|
1474
|
+
"\nStream error. Make sure backend is running:\n buildwithnexus server"
|
|
1475
|
+
);
|
|
1476
|
+
process.exit(1);
|
|
1425
1477
|
}
|
|
1426
|
-
|
|
1427
|
-
|
|
1478
|
+
} catch (error) {
|
|
1479
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1480
|
+
console.error("Error:", message);
|
|
1481
|
+
process.exit(1);
|
|
1428
1482
|
}
|
|
1429
1483
|
}
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1484
|
+
|
|
1485
|
+
// src/cli/dashboard-command.ts
|
|
1486
|
+
import { spawn } from "child_process";
|
|
1487
|
+
import { fileURLToPath } from "url";
|
|
1488
|
+
import path4 from "path";
|
|
1489
|
+
var __dirname = path4.dirname(fileURLToPath(import.meta.url));
|
|
1490
|
+
async function dashboardCommand(options) {
|
|
1491
|
+
const port = options.port || "4201";
|
|
1492
|
+
console.log(`Starting Nexus Dashboard on http://localhost:${port}
|
|
1493
|
+
`);
|
|
1494
|
+
const dashboardPath = path4.join(__dirname, "../deep-agents/dashboard/server.js");
|
|
1495
|
+
const dashboard = spawn("node", [dashboardPath], {
|
|
1496
|
+
env: { ...process.env, PORT: port },
|
|
1497
|
+
stdio: "inherit"
|
|
1498
|
+
});
|
|
1499
|
+
dashboard.on("error", (err) => {
|
|
1500
|
+
console.error("Failed to start dashboard:", err);
|
|
1501
|
+
process.exit(1);
|
|
1502
|
+
});
|
|
1503
|
+
console.log(`Dashboard ready! Open: http://localhost:${port}`);
|
|
1504
|
+
console.log("Press Ctrl+C to stop\n");
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// src/cli/interactive.ts
|
|
1508
|
+
import * as readline2 from "readline";
|
|
1509
|
+
import chalk3 from "chalk";
|
|
1510
|
+
|
|
1511
|
+
// src/cli/intent-classifier.ts
|
|
1512
|
+
var PLAN_KEYWORDS = [
|
|
1513
|
+
"design",
|
|
1514
|
+
"plan",
|
|
1515
|
+
"architect",
|
|
1516
|
+
"structure",
|
|
1517
|
+
"outline",
|
|
1518
|
+
"roadmap",
|
|
1519
|
+
"strategy",
|
|
1520
|
+
"organize",
|
|
1521
|
+
"breakdown",
|
|
1522
|
+
"scope",
|
|
1523
|
+
"model",
|
|
1524
|
+
"schema"
|
|
1525
|
+
];
|
|
1526
|
+
var BUILD_KEYWORDS = [
|
|
1527
|
+
"build",
|
|
1528
|
+
"create",
|
|
1529
|
+
"make",
|
|
1530
|
+
"write",
|
|
1531
|
+
"implement",
|
|
1532
|
+
"code",
|
|
1533
|
+
"generate",
|
|
1534
|
+
"add",
|
|
1535
|
+
"fix",
|
|
1536
|
+
"update",
|
|
1537
|
+
"deploy",
|
|
1538
|
+
"run",
|
|
1539
|
+
"start",
|
|
1540
|
+
"launch",
|
|
1541
|
+
"install",
|
|
1542
|
+
"set up",
|
|
1543
|
+
"setup",
|
|
1544
|
+
"refactor",
|
|
1545
|
+
"migrate"
|
|
1546
|
+
];
|
|
1547
|
+
var BRAINSTORM_KEYWORDS = [
|
|
1548
|
+
"what",
|
|
1549
|
+
"should",
|
|
1550
|
+
"idea",
|
|
1551
|
+
"ideas",
|
|
1552
|
+
"think",
|
|
1553
|
+
"consider",
|
|
1554
|
+
"suggest",
|
|
1555
|
+
"brainstorm",
|
|
1556
|
+
"explore",
|
|
1557
|
+
"wonder",
|
|
1558
|
+
"might",
|
|
1559
|
+
"could",
|
|
1560
|
+
"would",
|
|
1561
|
+
"how about",
|
|
1562
|
+
"what if",
|
|
1563
|
+
"options",
|
|
1564
|
+
"alternatives",
|
|
1565
|
+
"thoughts"
|
|
1566
|
+
];
|
|
1567
|
+
function classifyIntent(task) {
|
|
1568
|
+
const lower = task.toLowerCase().trim();
|
|
1569
|
+
let planScore = 0;
|
|
1570
|
+
let buildScore = 0;
|
|
1571
|
+
let brainstormScore = 0;
|
|
1572
|
+
for (const kw of PLAN_KEYWORDS) {
|
|
1573
|
+
if (lower.includes(kw)) planScore++;
|
|
1574
|
+
}
|
|
1575
|
+
for (const kw of BUILD_KEYWORDS) {
|
|
1576
|
+
if (lower.includes(kw)) buildScore++;
|
|
1577
|
+
}
|
|
1578
|
+
for (const kw of BRAINSTORM_KEYWORDS) {
|
|
1579
|
+
if (lower.includes(kw)) brainstormScore++;
|
|
1580
|
+
}
|
|
1581
|
+
const wordCount = lower.split(/\s+/).length;
|
|
1582
|
+
if (wordCount > 12 && planScore === buildScore && buildScore === brainstormScore) {
|
|
1583
|
+
return "plan";
|
|
1584
|
+
}
|
|
1585
|
+
if (brainstormScore > planScore && brainstormScore > buildScore) return "brainstorm";
|
|
1586
|
+
if (buildScore > planScore && buildScore > brainstormScore) return "build";
|
|
1587
|
+
if (planScore > 0) return "plan";
|
|
1588
|
+
return wordCount > 6 ? "plan" : "build";
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
// src/cli/interactive.ts
|
|
1592
|
+
init_secrets();
|
|
1593
|
+
init_docker();
|
|
1594
|
+
|
|
1595
|
+
// src/core/version.ts
|
|
1596
|
+
var resolvedVersion = true ? "0.7.3" : pkg.version;
|
|
1597
|
+
|
|
1598
|
+
// src/cli/interactive.ts
|
|
1599
|
+
var appVersion = resolvedVersion;
|
|
1600
|
+
async function interactiveMode() {
|
|
1601
|
+
const backendUrl = process.env.BACKEND_URL || "http://localhost:4200";
|
|
1602
|
+
const urlCheck = validateBackendUrl(backendUrl);
|
|
1603
|
+
if (!urlCheck.valid) {
|
|
1604
|
+
console.error(chalk3.red(`
|
|
1605
|
+
${urlCheck.error}`));
|
|
1606
|
+
process.exit(1);
|
|
1607
|
+
}
|
|
1608
|
+
if (!hasAnyKey()) {
|
|
1434
1609
|
try {
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1610
|
+
const storedKeys = loadKeys();
|
|
1611
|
+
if (storedKeys) {
|
|
1612
|
+
if (storedKeys.ANTHROPIC_API_KEY) process.env.ANTHROPIC_API_KEY = storedKeys.ANTHROPIC_API_KEY;
|
|
1613
|
+
if (storedKeys.OPENAI_API_KEY) process.env.OPENAI_API_KEY = storedKeys.OPENAI_API_KEY;
|
|
1614
|
+
if (storedKeys.GOOGLE_API_KEY) process.env.GOOGLE_API_KEY = storedKeys.GOOGLE_API_KEY;
|
|
1615
|
+
}
|
|
1438
1616
|
} catch {
|
|
1439
1617
|
}
|
|
1440
|
-
await new Promise((r) => setTimeout(r, 3e3));
|
|
1441
1618
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1619
|
+
if (!hasAnyKey()) {
|
|
1620
|
+
console.log(chalk3.cyan("\n\u{1F527} Configure API Keys\n"));
|
|
1621
|
+
const { deepAgentsInitCommand: deepAgentsInitCommand2 } = await Promise.resolve().then(() => (init_init_command(), init_command_exports));
|
|
1622
|
+
await deepAgentsInitCommand2();
|
|
1623
|
+
if (!hasAnyKey()) {
|
|
1624
|
+
console.error("Error: At least one API key is required to use buildwithnexus.");
|
|
1625
|
+
process.exit(1);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
const keys = loadApiKeys();
|
|
1629
|
+
console.log(chalk3.green("\n\u2713 Keys configured!"));
|
|
1630
|
+
console.log(chalk3.gray(` Anthropic: ${keys.anthropic ? "\u2713" : "\u2717"}`));
|
|
1631
|
+
console.log(chalk3.gray(` OpenAI: ${keys.openai ? "\u2713" : "\u2717"}`));
|
|
1632
|
+
console.log(chalk3.gray(` Google: ${keys.google ? "\u2713" : "\u2717"}`));
|
|
1633
|
+
console.log(chalk3.gray(` (Run 'da-init' to reconfigure)
|
|
1634
|
+
`));
|
|
1635
|
+
async function waitForBackend() {
|
|
1636
|
+
for (let i = 0; i < 5; i++) {
|
|
1637
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
1638
|
+
if (await checkServerHealth(backendUrl)) return true;
|
|
1639
|
+
}
|
|
1455
1640
|
return false;
|
|
1456
1641
|
}
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1642
|
+
if (!await checkServerHealth(backendUrl)) {
|
|
1643
|
+
console.log(chalk3.yellow("\u26A0\uFE0F Backend not accessible, attempting to start..."));
|
|
1644
|
+
await startBackend();
|
|
1645
|
+
const ready = await waitForBackend();
|
|
1646
|
+
if (!ready) {
|
|
1647
|
+
console.error(chalk3.red("\u274C Backend failed to start. Run: buildwithnexus server"));
|
|
1648
|
+
process.exit(1);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
const rl = readline2.createInterface({
|
|
1652
|
+
input: process.stdin,
|
|
1653
|
+
output: process.stdout
|
|
1654
|
+
});
|
|
1655
|
+
const ask = (question) => new Promise((resolve) => rl.question(question, resolve));
|
|
1656
|
+
console.clear();
|
|
1657
|
+
console.log(chalk3.gray("Welcome! Describe what you want the AI agents to do."));
|
|
1658
|
+
console.log(chalk3.gray('Type "exit" to quit.\n'));
|
|
1659
|
+
while (true) {
|
|
1660
|
+
const task = await ask(chalk3.bold.blue("\u{1F4DD} Task: "));
|
|
1661
|
+
if (task.toLowerCase() === "exit") {
|
|
1662
|
+
console.log(chalk3.yellow("\nGoodbye! \u{1F44B}\n"));
|
|
1663
|
+
rl.close();
|
|
1664
|
+
process.exit(0);
|
|
1665
|
+
}
|
|
1666
|
+
if (!task.trim()) {
|
|
1667
|
+
console.log(chalk3.red("Please enter a task.\n"));
|
|
1668
|
+
continue;
|
|
1669
|
+
}
|
|
1670
|
+
const suggestedMode = classifyIntent(task).toUpperCase();
|
|
1671
|
+
tui.displaySuggestedMode(suggestedMode, task);
|
|
1672
|
+
const currentMode = await selectMode(suggestedMode, ask);
|
|
1673
|
+
await runModeLoop(currentMode, task, backendUrl, ask);
|
|
1674
|
+
console.log("");
|
|
1467
1675
|
}
|
|
1468
1676
|
}
|
|
1469
|
-
async function
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1677
|
+
async function selectMode(suggested, ask) {
|
|
1678
|
+
const modeColor = {
|
|
1679
|
+
PLAN: chalk3.cyan,
|
|
1680
|
+
BUILD: chalk3.green,
|
|
1681
|
+
BRAINSTORM: chalk3.blue
|
|
1682
|
+
};
|
|
1683
|
+
console.log("");
|
|
1684
|
+
console.log(
|
|
1685
|
+
chalk3.gray("Press ") + chalk3.bold("Enter") + chalk3.gray(" to use ") + modeColor[suggested](suggested) + chalk3.gray(" or choose a mode:")
|
|
1686
|
+
);
|
|
1687
|
+
console.log(
|
|
1688
|
+
chalk3.gray(" ") + chalk3.cyan.bold("[1] PLAN") + chalk3.gray(" (p) design & break down steps") + chalk3.gray("\n ") + chalk3.green.bold("[2] BUILD") + chalk3.gray(" (b) execute with live streaming") + chalk3.gray("\n ") + chalk3.blue.bold("[3] BRAINSTORM") + chalk3.gray(" (bs) free-form explore & Q&A")
|
|
1689
|
+
);
|
|
1690
|
+
const answer = await ask(chalk3.gray("> "));
|
|
1691
|
+
const lower = answer.trim().toLowerCase();
|
|
1692
|
+
if (lower === "1" || lower === "p" || lower === "plan") return "PLAN";
|
|
1693
|
+
if (lower === "2" || lower === "b" || lower === "build") return "BUILD";
|
|
1694
|
+
if (lower === "3" || lower === "bs" || lower === "br" || lower === "brainstorm") return "BRAINSTORM";
|
|
1695
|
+
return suggested;
|
|
1696
|
+
}
|
|
1697
|
+
async function runModeLoop(mode, task, backendUrl, ask) {
|
|
1698
|
+
let currentMode = mode;
|
|
1699
|
+
while (true) {
|
|
1700
|
+
console.clear();
|
|
1701
|
+
printAppHeader();
|
|
1702
|
+
tui.displayModeBar(currentMode);
|
|
1703
|
+
tui.displayModeHeader(currentMode);
|
|
1704
|
+
if (currentMode === "PLAN") {
|
|
1705
|
+
const next = await planModeLoop(task, backendUrl, ask);
|
|
1706
|
+
if (next === "BUILD") {
|
|
1707
|
+
currentMode = "BUILD";
|
|
1708
|
+
continue;
|
|
1709
|
+
}
|
|
1710
|
+
if (next === "switch") {
|
|
1711
|
+
currentMode = await promptModeSwitch(currentMode, ask);
|
|
1712
|
+
continue;
|
|
1713
|
+
}
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
if (currentMode === "BUILD") {
|
|
1717
|
+
const next = await buildModeLoop(task, backendUrl, ask);
|
|
1718
|
+
if (next === "switch") {
|
|
1719
|
+
currentMode = await promptModeSwitch(currentMode, ask);
|
|
1720
|
+
continue;
|
|
1721
|
+
}
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
if (currentMode === "BRAINSTORM") {
|
|
1725
|
+
const next = await brainstormModeLoop(task, backendUrl, ask);
|
|
1726
|
+
if (next === "switch") {
|
|
1727
|
+
currentMode = await promptModeSwitch(currentMode, ask);
|
|
1728
|
+
continue;
|
|
1729
|
+
}
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1489
1732
|
}
|
|
1490
1733
|
}
|
|
1491
|
-
|
|
1492
|
-
log.
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1734
|
+
function printAppHeader() {
|
|
1735
|
+
console.log(chalk3.cyan("\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\u2550\u2550\u2557"));
|
|
1736
|
+
console.log(
|
|
1737
|
+
chalk3.cyan("\u2551") + chalk3.bold.white(" Nexus - Autonomous Agent Orchestration ") + chalk3.cyan("\u2551")
|
|
1738
|
+
);
|
|
1739
|
+
const versionLine = ` v${appVersion}`;
|
|
1740
|
+
console.log(
|
|
1741
|
+
chalk3.cyan("\u2551") + chalk3.dim(versionLine.padEnd(60)) + chalk3.cyan("\u2551")
|
|
1742
|
+
);
|
|
1743
|
+
console.log(chalk3.cyan("\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\u2550\u2550\u255D"));
|
|
1744
|
+
console.log("");
|
|
1500
1745
|
}
|
|
1501
|
-
async function
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1746
|
+
async function promptModeSwitch(current, ask) {
|
|
1747
|
+
const others = ["PLAN", "BUILD", "BRAINSTORM"].filter((m) => m !== current);
|
|
1748
|
+
console.log("");
|
|
1749
|
+
console.log(
|
|
1750
|
+
chalk3.gray("Switch to: ") + others.map((m, i) => chalk3.bold(`[${i + 1}] ${m}`)).join(chalk3.gray(" ")) + chalk3.gray(" [Enter to stay in ") + chalk3.bold(current) + chalk3.gray("]")
|
|
1751
|
+
);
|
|
1752
|
+
const answer = await ask(chalk3.gray("> "));
|
|
1753
|
+
const n = parseInt(answer.trim(), 10);
|
|
1754
|
+
if (n === 1) return others[0];
|
|
1755
|
+
if (n === 2) return others[1];
|
|
1756
|
+
return current;
|
|
1509
1757
|
}
|
|
1510
|
-
async function
|
|
1758
|
+
async function planModeLoop(task, backendUrl, ask) {
|
|
1759
|
+
console.log(chalk3.bold("Task:"), chalk3.white(task));
|
|
1760
|
+
console.log("");
|
|
1761
|
+
console.log(chalk3.yellow("\u23F3 Fetching plan from backend..."));
|
|
1762
|
+
let steps = [];
|
|
1511
1763
|
try {
|
|
1512
|
-
const
|
|
1513
|
-
"
|
|
1514
|
-
"
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1764
|
+
const response = await fetch(`${backendUrl}/api/run`, {
|
|
1765
|
+
method: "POST",
|
|
1766
|
+
headers: { "Content-Type": "application/json" },
|
|
1767
|
+
body: JSON.stringify(buildRunPayload(task, "engineer", "")),
|
|
1768
|
+
signal: AbortSignal.timeout(12e4)
|
|
1769
|
+
});
|
|
1770
|
+
if (!response.ok) {
|
|
1771
|
+
console.error(chalk3.red("Backend error \u2014 cannot fetch plan."));
|
|
1772
|
+
return "cancel";
|
|
1773
|
+
}
|
|
1774
|
+
const { run_id } = await response.json();
|
|
1775
|
+
tui.displayConnected(run_id);
|
|
1776
|
+
const streamResponse = await fetch(`${backendUrl}/api/stream/${run_id}`, { signal: AbortSignal.timeout(12e4) });
|
|
1777
|
+
const reader = streamResponse.body?.getReader();
|
|
1778
|
+
if (!reader) throw new Error("No response body");
|
|
1779
|
+
let planReceived = false;
|
|
1780
|
+
for await (const parsed of parseSSEStream(reader)) {
|
|
1781
|
+
if (parsed.type === "plan") {
|
|
1782
|
+
steps = parsed.data["steps"] || [];
|
|
1783
|
+
planReceived = true;
|
|
1784
|
+
reader.cancel();
|
|
1785
|
+
break;
|
|
1786
|
+
} else if (parsed.type === "error") {
|
|
1787
|
+
const errorMsg = parsed.data["error"] || parsed.data["content"] || "Unknown error";
|
|
1788
|
+
tui.displayError(errorMsg);
|
|
1789
|
+
reader.cancel();
|
|
1790
|
+
return "cancel";
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
if (!planReceived || steps.length === 0) {
|
|
1794
|
+
console.log(chalk3.yellow("No plan received from backend."));
|
|
1795
|
+
steps = ["(no steps returned \u2014 execute anyway?)"];
|
|
1536
1796
|
}
|
|
1537
|
-
return;
|
|
1538
|
-
}
|
|
1539
|
-
const platform = detectPlatform();
|
|
1540
|
-
log.step(`Installing Docker for ${platform.os} (${platform.arch})...`);
|
|
1541
|
-
try {
|
|
1542
|
-
await installDocker(platform);
|
|
1543
1797
|
} catch (err) {
|
|
1544
1798
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
}
|
|
1548
|
-
spinner.text = "Verifying Docker installation...";
|
|
1549
|
-
spinner.start();
|
|
1550
|
-
const verified = await isDockerInstalled();
|
|
1551
|
-
if (!verified) {
|
|
1552
|
-
fail(spinner, "Docker installation could not be verified");
|
|
1553
|
-
log.error(
|
|
1554
|
-
"Docker was installed but is not responding.\n\n Please ensure Docker is running, then verify with:\n docker --version\n\n Once Docker is running, you can proceed with:\n buildwithnexus init"
|
|
1555
|
-
);
|
|
1556
|
-
process.exit(1);
|
|
1557
|
-
}
|
|
1558
|
-
try {
|
|
1559
|
-
const { stdout } = await execa2("docker", ["--version"]);
|
|
1560
|
-
succeed(spinner, `Docker verified: ${stdout.trim()}`);
|
|
1561
|
-
} catch {
|
|
1562
|
-
succeed(spinner, "Docker is installed and running");
|
|
1563
|
-
}
|
|
1564
|
-
log.success("\nDocker setup complete! You can now run:\n\n buildwithnexus init\n");
|
|
1565
|
-
});
|
|
1566
|
-
|
|
1567
|
-
// src/commands/init.ts
|
|
1568
|
-
init_banner();
|
|
1569
|
-
import { Command as Command2 } from "commander";
|
|
1570
|
-
import chalk7 from "chalk";
|
|
1571
|
-
|
|
1572
|
-
// src/ui/prompts.ts
|
|
1573
|
-
import { confirm, password } from "@inquirer/prompts";
|
|
1574
|
-
import chalk6 from "chalk";
|
|
1575
|
-
|
|
1576
|
-
// src/core/dlp.ts
|
|
1577
|
-
import crypto from "crypto";
|
|
1578
|
-
import fs2 from "fs";
|
|
1579
|
-
import path4 from "path";
|
|
1580
|
-
var NEXUS_HOME = path4.join(process.env.HOME || "~", ".buildwithnexus");
|
|
1581
|
-
var SECRET_PATTERNS = [
|
|
1582
|
-
/sk-ant-api03-[A-Za-z0-9_-]{20,}/g,
|
|
1583
|
-
// Anthropic API key
|
|
1584
|
-
/sk-[A-Za-z0-9]{20,}/g,
|
|
1585
|
-
// OpenAI API key
|
|
1586
|
-
/AIza[A-Za-z0-9_-]{35}/g
|
|
1587
|
-
// Google AI API key
|
|
1588
|
-
];
|
|
1589
|
-
var FORBIDDEN_KEY_CHARS = /[\n\r\t'"\\`${}();&|<>!#%^]/;
|
|
1590
|
-
var KEY_VALIDATORS = {
|
|
1591
|
-
ANTHROPIC_API_KEY: /^sk-ant-[A-Za-z0-9_-]{20,}$/,
|
|
1592
|
-
OPENAI_API_KEY: /^sk-[A-Za-z0-9_-]{20,}$/,
|
|
1593
|
-
GOOGLE_API_KEY: /^AIza[A-Za-z0-9_-]{35,}$/,
|
|
1594
|
-
NEXUS_MASTER_SECRET: /^[A-Za-z0-9_-]{20,64}$/
|
|
1595
|
-
};
|
|
1596
|
-
function shellEscape(value) {
|
|
1597
|
-
if (value.includes("\0")) {
|
|
1598
|
-
throw new DlpViolation("Null byte in shell argument");
|
|
1799
|
+
console.error(chalk3.red("Error: " + msg));
|
|
1800
|
+
return "cancel";
|
|
1599
1801
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1802
|
+
displayPlanSteps(steps);
|
|
1803
|
+
while (true) {
|
|
1804
|
+
console.log(chalk3.gray("Options: ") + chalk3.bold("[Y]") + chalk3.gray(" Execute ") + chalk3.bold("[e]") + chalk3.gray(" Edit step ") + chalk3.bold("[s]") + chalk3.gray(" Switch mode ") + chalk3.bold("[Esc/n]") + chalk3.gray(" Cancel"));
|
|
1805
|
+
const answer = (await ask(tui.displayPermissionPrompt("Execute this plan?"))).trim().toLowerCase();
|
|
1806
|
+
if (answer === "" || answer === "y") {
|
|
1807
|
+
return "BUILD";
|
|
1808
|
+
}
|
|
1809
|
+
if (answer === "n" || answer === "\x1B") {
|
|
1810
|
+
console.log(chalk3.yellow("\nExecution cancelled.\n"));
|
|
1811
|
+
return "cancel";
|
|
1812
|
+
}
|
|
1813
|
+
if (answer === "e" || answer === "edit") {
|
|
1814
|
+
steps = await editPlanSteps(steps, ask);
|
|
1815
|
+
displayPlanSteps(steps);
|
|
1816
|
+
continue;
|
|
1817
|
+
}
|
|
1818
|
+
if (answer === "s" || answer === "switch") {
|
|
1819
|
+
return "switch";
|
|
1820
|
+
}
|
|
1606
1821
|
}
|
|
1607
|
-
return result;
|
|
1608
1822
|
}
|
|
1609
|
-
function
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1823
|
+
function displayPlanSteps(steps) {
|
|
1824
|
+
console.log("");
|
|
1825
|
+
console.log(chalk3.bold.cyan("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
1826
|
+
console.log(chalk3.bold.cyan("\u2502") + chalk3.bold.white(" \u{1F4CB} Execution Plan ") + chalk3.bold.cyan("\u2502"));
|
|
1827
|
+
console.log(chalk3.bold.cyan("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
|
|
1828
|
+
steps.forEach((step, i) => {
|
|
1829
|
+
const label = ` Step ${i + 1}: `;
|
|
1830
|
+
const maxContentWidth = 57 - label.length;
|
|
1831
|
+
const truncated = step.length > maxContentWidth ? step.substring(0, maxContentWidth - 3) + "..." : step;
|
|
1832
|
+
const line = label + truncated;
|
|
1833
|
+
const padded = line.padEnd(57);
|
|
1834
|
+
console.log(chalk3.bold.cyan("\u2502") + chalk3.white(padded) + chalk3.bold.cyan("\u2502"));
|
|
1835
|
+
});
|
|
1836
|
+
console.log(chalk3.bold.cyan("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
1837
|
+
console.log("");
|
|
1616
1838
|
}
|
|
1617
|
-
function
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1839
|
+
async function editPlanSteps(steps, ask) {
|
|
1840
|
+
console.log(chalk3.gray("Enter step number to edit, or press Enter to finish editing:"));
|
|
1841
|
+
const numStr = await ask(chalk3.bold("Step #: "));
|
|
1842
|
+
const n = parseInt(numStr.trim(), 10);
|
|
1843
|
+
if (!isNaN(n) && n >= 1 && n <= steps.length) {
|
|
1844
|
+
console.log(chalk3.gray(`Current: ${steps[n - 1]}`));
|
|
1845
|
+
const updated = await ask(chalk3.bold("New text: "));
|
|
1846
|
+
if (updated.trim()) steps[n - 1] = updated.trim();
|
|
1623
1847
|
}
|
|
1624
|
-
return
|
|
1848
|
+
return steps;
|
|
1625
1849
|
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
)
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
);
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
)
|
|
1850
|
+
async function buildModeLoop(task, backendUrl, ask) {
|
|
1851
|
+
console.log(chalk3.bold("Task:"), chalk3.white(task));
|
|
1852
|
+
tui.displayConnecting();
|
|
1853
|
+
try {
|
|
1854
|
+
const response = await fetch(`${backendUrl}/api/run`, {
|
|
1855
|
+
method: "POST",
|
|
1856
|
+
headers: { "Content-Type": "application/json" },
|
|
1857
|
+
body: JSON.stringify(buildRunPayload(task, "engineer", "")),
|
|
1858
|
+
signal: AbortSignal.timeout(12e4)
|
|
1859
|
+
});
|
|
1860
|
+
if (!response.ok) {
|
|
1861
|
+
console.error(chalk3.red("Backend error"));
|
|
1862
|
+
return "done";
|
|
1863
|
+
}
|
|
1864
|
+
const { run_id } = await response.json();
|
|
1865
|
+
tui.displayConnected(run_id);
|
|
1866
|
+
console.log(chalk3.bold.green("\u2699\uFE0F Executing..."));
|
|
1867
|
+
tui.displayStreamStart();
|
|
1868
|
+
const streamResponse = await fetch(`${backendUrl}/api/stream/${run_id}`, { signal: AbortSignal.timeout(12e4) });
|
|
1869
|
+
const reader = streamResponse.body?.getReader();
|
|
1870
|
+
if (!reader) throw new Error("No response body");
|
|
1871
|
+
for await (const parsed of parseSSEStream(reader)) {
|
|
1872
|
+
const type = parsed.type;
|
|
1873
|
+
if (type === "execution_complete") {
|
|
1874
|
+
const summary = parsed.data["summary"] || "";
|
|
1875
|
+
const count = parsed.data["todos_completed"] || 0;
|
|
1876
|
+
tui.displayResults(summary, count);
|
|
1877
|
+
tui.displayComplete(tui.getElapsedTime());
|
|
1878
|
+
break;
|
|
1879
|
+
} else if (type === "done") {
|
|
1880
|
+
tui.displayEvent(type, { content: "Task completed successfully" });
|
|
1881
|
+
tui.displayComplete(tui.getElapsedTime());
|
|
1882
|
+
break;
|
|
1883
|
+
} else if (type === "error") {
|
|
1884
|
+
const errorMsg = parsed.data["error"] || parsed.data["content"] || "Unknown error";
|
|
1885
|
+
tui.displayError(errorMsg);
|
|
1886
|
+
break;
|
|
1887
|
+
} else if (type !== "plan") {
|
|
1888
|
+
tui.displayEvent(type, parsed.data);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
} catch (err) {
|
|
1892
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1893
|
+
console.error(chalk3.red("Error: " + msg));
|
|
1648
1894
|
}
|
|
1895
|
+
console.log("");
|
|
1896
|
+
console.log(
|
|
1897
|
+
chalk3.gray("Options: ") + chalk3.bold("[Enter]") + chalk3.gray(" Done ") + chalk3.bold("[s]") + chalk3.gray(" Switch mode")
|
|
1898
|
+
);
|
|
1899
|
+
const answer = (await ask(chalk3.bold("> "))).trim().toLowerCase();
|
|
1900
|
+
if (answer === "s" || answer === "switch") return "switch";
|
|
1901
|
+
return "done";
|
|
1649
1902
|
}
|
|
1650
|
-
function
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1903
|
+
async function brainstormModeLoop(task, backendUrl, ask) {
|
|
1904
|
+
console.log(chalk3.bold("Starting topic:"), chalk3.white(task));
|
|
1905
|
+
console.log(chalk3.gray('Ask follow-up questions. Type "done" to exit, "switch" to change mode.\n'));
|
|
1906
|
+
let currentQuestion = task;
|
|
1907
|
+
while (true) {
|
|
1908
|
+
console.log(chalk3.bold.blue("\u{1F4A1} Thinking..."));
|
|
1654
1909
|
try {
|
|
1655
|
-
|
|
1910
|
+
const response = await fetch(`${backendUrl}/api/run`, {
|
|
1911
|
+
method: "POST",
|
|
1912
|
+
headers: { "Content-Type": "application/json" },
|
|
1913
|
+
body: JSON.stringify(buildRunPayload(
|
|
1914
|
+
currentQuestion,
|
|
1915
|
+
"brainstorm",
|
|
1916
|
+
"Generate ideas, considerations, and suggestions. Be concise and helpful."
|
|
1917
|
+
)),
|
|
1918
|
+
signal: AbortSignal.timeout(12e4)
|
|
1919
|
+
});
|
|
1920
|
+
if (response.ok) {
|
|
1921
|
+
const { run_id } = await response.json();
|
|
1922
|
+
const streamResponse = await fetch(`${backendUrl}/api/stream/${run_id}`, { signal: AbortSignal.timeout(12e4) });
|
|
1923
|
+
const reader = streamResponse.body?.getReader();
|
|
1924
|
+
if (reader) {
|
|
1925
|
+
let responseText = "";
|
|
1926
|
+
for await (const parsed of parseSSEStream(reader)) {
|
|
1927
|
+
const type = parsed.type;
|
|
1928
|
+
const data = parsed.data;
|
|
1929
|
+
if (type === "done" || type === "execution_complete") {
|
|
1930
|
+
const summary = data["summary"] || "";
|
|
1931
|
+
if (summary) responseText = summary;
|
|
1932
|
+
break;
|
|
1933
|
+
} else if (type === "error") {
|
|
1934
|
+
const errorMsg = data["error"] || data["content"] || "Unknown error";
|
|
1935
|
+
responseText += errorMsg + "\n";
|
|
1936
|
+
break;
|
|
1937
|
+
} else if (type === "thought" || type === "observation") {
|
|
1938
|
+
const content = data["content"] || "";
|
|
1939
|
+
if (content) responseText += content + "\n";
|
|
1940
|
+
} else if (type === "agent_response" || type === "agent_result") {
|
|
1941
|
+
const content = data["content"] || data["result"] || "";
|
|
1942
|
+
if (content) responseText += content + "\n";
|
|
1943
|
+
} else if (type === "action") {
|
|
1944
|
+
const content = data["content"] || "";
|
|
1945
|
+
if (content) responseText += content + "\n";
|
|
1946
|
+
} else if (type === "agent_working") {
|
|
1947
|
+
} else if (type !== "plan") {
|
|
1948
|
+
const content = data["content"] || data["response"] || "";
|
|
1949
|
+
if (content) responseText += content + "\n";
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
if (responseText.trim()) {
|
|
1953
|
+
tui.displayBrainstormResponse(responseText.trim());
|
|
1954
|
+
} else {
|
|
1955
|
+
console.log(chalk3.gray("(No response received from agent)"));
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
} else {
|
|
1959
|
+
console.log(chalk3.red("Could not reach backend for brainstorm response."));
|
|
1960
|
+
}
|
|
1656
1961
|
} catch (err) {
|
|
1657
|
-
|
|
1962
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1963
|
+
console.error(chalk3.red("Error: " + msg));
|
|
1658
1964
|
}
|
|
1965
|
+
const followUp = await ask(chalk3.bold.blue("\u{1F4AC} You: "));
|
|
1966
|
+
const lower = followUp.trim().toLowerCase();
|
|
1967
|
+
if (lower === "done" || lower === "exit") return "done";
|
|
1968
|
+
if (lower === "switch") return "switch";
|
|
1969
|
+
if (!followUp.trim()) continue;
|
|
1970
|
+
currentQuestion = followUp.trim();
|
|
1659
1971
|
}
|
|
1660
|
-
return violations;
|
|
1661
1972
|
}
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1973
|
+
|
|
1974
|
+
// src/commands/install.ts
|
|
1975
|
+
import { Command } from "commander";
|
|
1976
|
+
import { execa as execa2 } from "execa";
|
|
1977
|
+
|
|
1978
|
+
// src/ui/spinner.ts
|
|
1979
|
+
import ora from "ora";
|
|
1980
|
+
import chalk4 from "chalk";
|
|
1981
|
+
function createSpinner(text) {
|
|
1982
|
+
return ora({ text, color: "cyan", spinner: "dots" });
|
|
1666
1983
|
}
|
|
1667
|
-
function
|
|
1668
|
-
|
|
1669
|
-
fs2.writeFileSync(HMAC_PATH, hmac, { mode: 384 });
|
|
1984
|
+
function succeed(spinner, text) {
|
|
1985
|
+
spinner.succeed(chalk4.green(text));
|
|
1670
1986
|
}
|
|
1671
|
-
function
|
|
1672
|
-
|
|
1673
|
-
const hmacExist = fs2.existsSync(HMAC_PATH);
|
|
1674
|
-
if (!keysExist && !hmacExist) return true;
|
|
1675
|
-
if (keysExist && !hmacExist) return false;
|
|
1676
|
-
try {
|
|
1677
|
-
const stored = fs2.readFileSync(HMAC_PATH, "utf-8").trim();
|
|
1678
|
-
const computed = computeFileHmac(keysPath, masterSecret);
|
|
1679
|
-
return crypto.timingSafeEqual(
|
|
1680
|
-
Buffer.from(stored, "hex"),
|
|
1681
|
-
Buffer.from(computed, "hex")
|
|
1682
|
-
);
|
|
1683
|
-
} catch {
|
|
1684
|
-
return false;
|
|
1685
|
-
}
|
|
1987
|
+
function fail(spinner, text) {
|
|
1988
|
+
spinner.fail(chalk4.red(text));
|
|
1686
1989
|
}
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
if (fs2.existsSync(rotated)) fs2.unlinkSync(rotated);
|
|
1698
|
-
fs2.renameSync(AUDIT_PATH, rotated);
|
|
1699
|
-
try {
|
|
1700
|
-
fs2.chmodSync(rotated, 384);
|
|
1701
|
-
} catch {
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
const line = `${(/* @__PURE__ */ new Date()).toISOString()} | ${event} | ${redact(detail)}
|
|
1706
|
-
`;
|
|
1707
|
-
fs2.appendFileSync(AUDIT_PATH, line, { mode: 384 });
|
|
1990
|
+
|
|
1991
|
+
// src/commands/install.ts
|
|
1992
|
+
init_logger();
|
|
1993
|
+
init_platform();
|
|
1994
|
+
init_docker();
|
|
1995
|
+
var installCommand = new Command("install").description("Install Docker and configure system prerequisites").action(async () => {
|
|
1996
|
+
const spinner = createSpinner("");
|
|
1997
|
+
log.step("Checking Docker installation...");
|
|
1998
|
+
const alreadyInstalled = await isDockerInstalled();
|
|
1999
|
+
if (alreadyInstalled) {
|
|
1708
2000
|
try {
|
|
1709
|
-
|
|
2001
|
+
const { stdout } = await execa2("docker", ["--version"]);
|
|
2002
|
+
log.success(`Docker is already installed and running: ${stdout.trim()}`);
|
|
1710
2003
|
} catch {
|
|
2004
|
+
log.success("Docker is already installed and running.");
|
|
1711
2005
|
}
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
const platform = detectPlatform();
|
|
2009
|
+
log.step(`Installing Docker for ${platform.os} (${platform.arch})...`);
|
|
2010
|
+
try {
|
|
2011
|
+
await installDocker(platform);
|
|
2012
|
+
} catch (err) {
|
|
2013
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2014
|
+
log.error(`Docker installation failed: ${msg}`);
|
|
2015
|
+
process.exit(1);
|
|
2016
|
+
}
|
|
2017
|
+
spinner.text = "Verifying Docker installation...";
|
|
2018
|
+
spinner.start();
|
|
2019
|
+
const verified = await isDockerInstalled();
|
|
2020
|
+
if (!verified) {
|
|
2021
|
+
fail(spinner, "Docker installation could not be verified");
|
|
2022
|
+
log.error(
|
|
2023
|
+
"Docker was installed but is not responding.\n\n Please ensure Docker is running, then verify with:\n docker --version\n\n Once Docker is running, you can proceed with:\n buildwithnexus init"
|
|
2024
|
+
);
|
|
2025
|
+
process.exit(1);
|
|
2026
|
+
}
|
|
2027
|
+
try {
|
|
2028
|
+
const { stdout } = await execa2("docker", ["--version"]);
|
|
2029
|
+
succeed(spinner, `Docker verified: ${stdout.trim()}`);
|
|
1712
2030
|
} catch {
|
|
2031
|
+
succeed(spinner, "Docker is installed and running");
|
|
1713
2032
|
}
|
|
1714
|
-
|
|
2033
|
+
log.success("\nDocker setup complete! You can now run:\n\n buildwithnexus init\n");
|
|
2034
|
+
});
|
|
2035
|
+
|
|
2036
|
+
// src/commands/init.ts
|
|
2037
|
+
init_banner();
|
|
2038
|
+
import { Command as Command2 } from "commander";
|
|
2039
|
+
import chalk7 from "chalk";
|
|
2040
|
+
init_logger();
|
|
1715
2041
|
|
|
1716
2042
|
// src/ui/prompts.ts
|
|
2043
|
+
init_dlp();
|
|
2044
|
+
import { confirm, password } from "@inquirer/prompts";
|
|
2045
|
+
import chalk6 from "chalk";
|
|
1717
2046
|
async function promptInitConfig() {
|
|
1718
2047
|
console.log(chalk6.bold("\n API Keys\n"));
|
|
1719
2048
|
const anthropicKey = await password({
|
|
@@ -1768,66 +2097,14 @@ async function promptInitConfig() {
|
|
|
1768
2097
|
};
|
|
1769
2098
|
}
|
|
1770
2099
|
|
|
1771
|
-
// src/
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
var NEXUS_HOME2 = process.env.NEXUS_HOME || path5.join(process.env.HOME || "~", ".buildwithnexus");
|
|
1776
|
-
var CONFIG_PATH = process.env.NEXUS_CONFIG_PATH || path5.join(NEXUS_HOME2, "config.json");
|
|
1777
|
-
var KEYS_PATH = process.env.NEXUS_KEYS_PATH || path5.join(NEXUS_HOME2, ".env.keys");
|
|
1778
|
-
function ensureHome() {
|
|
1779
|
-
fs3.mkdirSync(NEXUS_HOME2, { recursive: true, mode: 448 });
|
|
1780
|
-
fs3.mkdirSync(path5.join(NEXUS_HOME2, "vm", "images"), { recursive: true, mode: 448 });
|
|
1781
|
-
fs3.mkdirSync(path5.join(NEXUS_HOME2, "vm", "configs"), { recursive: true, mode: 448 });
|
|
1782
|
-
fs3.mkdirSync(path5.join(NEXUS_HOME2, "vm", "logs"), { recursive: true, mode: 448 });
|
|
1783
|
-
fs3.mkdirSync(path5.join(NEXUS_HOME2, "ssh"), { recursive: true, mode: 448 });
|
|
1784
|
-
}
|
|
1785
|
-
function generateMasterSecret() {
|
|
1786
|
-
return crypto2.randomBytes(32).toString("base64url");
|
|
1787
|
-
}
|
|
1788
|
-
function saveConfig(config) {
|
|
1789
|
-
const { masterSecret: _secret, ...safeConfig } = config;
|
|
1790
|
-
fs3.writeFileSync(CONFIG_PATH, JSON.stringify(safeConfig, null, 2), { mode: 384 });
|
|
1791
|
-
}
|
|
1792
|
-
function loadConfig() {
|
|
1793
|
-
if (!fs3.existsSync(CONFIG_PATH)) return null;
|
|
1794
|
-
return JSON.parse(fs3.readFileSync(CONFIG_PATH, "utf-8"));
|
|
1795
|
-
}
|
|
1796
|
-
function saveKeys(keys) {
|
|
1797
|
-
const violations = validateAllKeys(keys);
|
|
1798
|
-
if (violations.length > 0) {
|
|
1799
|
-
throw new DlpViolation(`Key validation failed: ${violations.join("; ")}`);
|
|
1800
|
-
}
|
|
1801
|
-
const lines = Object.entries(keys).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`);
|
|
1802
|
-
fs3.writeFileSync(KEYS_PATH, lines.join("\n") + "\n", { mode: 384 });
|
|
1803
|
-
sealKeysFile(KEYS_PATH, keys.NEXUS_MASTER_SECRET);
|
|
1804
|
-
audit("keys_saved", `${Object.keys(keys).filter((k) => keys[k]).length} keys saved`);
|
|
1805
|
-
}
|
|
1806
|
-
function loadKeys() {
|
|
1807
|
-
if (!fs3.existsSync(KEYS_PATH)) return null;
|
|
1808
|
-
const content = fs3.readFileSync(KEYS_PATH, "utf-8");
|
|
1809
|
-
const keys = {};
|
|
1810
|
-
for (const line of content.split("\n")) {
|
|
1811
|
-
const eq = line.indexOf("=");
|
|
1812
|
-
if (eq > 0) keys[line.slice(0, eq)] = line.slice(eq + 1);
|
|
1813
|
-
}
|
|
1814
|
-
const result = keys;
|
|
1815
|
-
if (result.NEXUS_MASTER_SECRET && !verifyKeysFile(KEYS_PATH, result.NEXUS_MASTER_SECRET)) {
|
|
1816
|
-
audit("keys_tampered", "HMAC mismatch on .env.keys");
|
|
1817
|
-
throw new DlpViolation(
|
|
1818
|
-
".env.keys has been modified outside of buildwithnexus. Run 'buildwithnexus keys set' to re-enter your keys, or 'buildwithnexus destroy' to start fresh."
|
|
1819
|
-
);
|
|
1820
|
-
}
|
|
1821
|
-
audit("keys_loaded", `${Object.keys(keys).length} keys loaded`);
|
|
1822
|
-
return result;
|
|
1823
|
-
}
|
|
1824
|
-
function maskKey(key) {
|
|
1825
|
-
if (key.length <= 8) return "***";
|
|
1826
|
-
const reveal = Math.min(4, Math.floor(key.length * 0.1));
|
|
1827
|
-
return key.slice(0, reveal) + "..." + key.slice(-reveal);
|
|
1828
|
-
}
|
|
2100
|
+
// src/commands/init.ts
|
|
2101
|
+
init_platform();
|
|
2102
|
+
init_secrets();
|
|
2103
|
+
init_docker();
|
|
1829
2104
|
|
|
1830
2105
|
// src/core/tunnel.ts
|
|
2106
|
+
init_docker();
|
|
2107
|
+
init_dlp();
|
|
1831
2108
|
var CLOUDFLARED_VERSION = "2024.12.2";
|
|
1832
2109
|
var CLOUDFLARED_SHA256 = {
|
|
1833
2110
|
amd64: "40ec9a0f5b58e3b04183aaf01c4ddd4dbc6af39b0f06be4b7ce8b1011d0a07ab",
|
|
@@ -1871,6 +2148,7 @@ async function startTunnel() {
|
|
|
1871
2148
|
}
|
|
1872
2149
|
|
|
1873
2150
|
// src/commands/init.ts
|
|
2151
|
+
init_dlp();
|
|
1874
2152
|
async function withSpinner(spinner, label, fn) {
|
|
1875
2153
|
spinner.text = label;
|
|
1876
2154
|
spinner.start();
|
|
@@ -1878,32 +2156,11 @@ async function withSpinner(spinner, label, fn) {
|
|
|
1878
2156
|
succeed(spinner, label);
|
|
1879
2157
|
return result;
|
|
1880
2158
|
}
|
|
1881
|
-
async function waitForHealthy(port, timeoutMs = 12e4) {
|
|
1882
|
-
const start = Date.now();
|
|
1883
|
-
let attempt = 0;
|
|
1884
|
-
const backoffMs = (n) => Math.min(2e3 * Math.pow(2, n), 1e4);
|
|
1885
|
-
while (Date.now() - start < timeoutMs) {
|
|
1886
|
-
try {
|
|
1887
|
-
const res = await fetch(`http://localhost:${port}/health`);
|
|
1888
|
-
if (res.ok) {
|
|
1889
|
-
const body = await res.text();
|
|
1890
|
-
if (body.includes("ok")) return true;
|
|
1891
|
-
}
|
|
1892
|
-
} catch {
|
|
1893
|
-
}
|
|
1894
|
-
const delay = backoffMs(attempt++);
|
|
1895
|
-
const remaining = timeoutMs - (Date.now() - start);
|
|
1896
|
-
if (remaining <= 0) break;
|
|
1897
|
-
await new Promise((r) => setTimeout(r, Math.min(delay, remaining)));
|
|
1898
|
-
}
|
|
1899
|
-
return false;
|
|
1900
|
-
}
|
|
1901
2159
|
var phases = [
|
|
1902
2160
|
// Phase 1 — Configuration (~30s)
|
|
1903
2161
|
{
|
|
1904
2162
|
name: "Configuration",
|
|
1905
2163
|
run: async (ctx) => {
|
|
1906
|
-
showBanner();
|
|
1907
2164
|
const platform = detectPlatform();
|
|
1908
2165
|
log.detail("Platform", `${platform.os} ${platform.arch}`);
|
|
1909
2166
|
const userConfig = await promptInitConfig();
|
|
@@ -1978,19 +2235,16 @@ var phases = [
|
|
|
1978
2235
|
name: "Launch",
|
|
1979
2236
|
run: async (ctx, spinner) => {
|
|
1980
2237
|
const { config, keys } = ctx;
|
|
1981
|
-
const alreadyRunning = await isNexusRunning();
|
|
1982
|
-
if (alreadyRunning) {
|
|
1983
|
-
await withSpinner(spinner, "Stopping existing NEXUS container...", () => stopNexus());
|
|
1984
|
-
}
|
|
1985
2238
|
await withSpinner(
|
|
1986
2239
|
spinner,
|
|
1987
2240
|
"Starting NEXUS container...",
|
|
1988
|
-
() =>
|
|
2241
|
+
() => launchNexus(
|
|
1989
2242
|
{
|
|
1990
2243
|
anthropic: keys.ANTHROPIC_API_KEY,
|
|
1991
2244
|
openai: keys.OPENAI_API_KEY || ""
|
|
1992
2245
|
},
|
|
1993
|
-
{ port: config.httpPort }
|
|
2246
|
+
{ port: config.httpPort },
|
|
2247
|
+
{ healthTimeoutMs: 0, stopExisting: true }
|
|
1994
2248
|
)
|
|
1995
2249
|
);
|
|
1996
2250
|
ctx.containerStarted = true;
|
|
@@ -2003,7 +2257,8 @@ var phases = [
|
|
|
2003
2257
|
const { config } = ctx;
|
|
2004
2258
|
spinner.text = `Waiting for NEXUS server on port ${config.httpPort}...`;
|
|
2005
2259
|
spinner.start();
|
|
2006
|
-
const
|
|
2260
|
+
const { waitForServer: waitForServer2 } = await Promise.resolve().then(() => (init_health(), health_exports));
|
|
2261
|
+
const healthy = await waitForServer2(12e4);
|
|
2007
2262
|
if (!healthy) {
|
|
2008
2263
|
fail(spinner, "Server failed to start within 120s");
|
|
2009
2264
|
log.warn("Check logs: docker logs nexus");
|
|
@@ -2086,39 +2341,9 @@ var initCommand = new Command2("init").description("Scaffold and launch a new NE
|
|
|
2086
2341
|
|
|
2087
2342
|
// src/commands/start.ts
|
|
2088
2343
|
import { Command as Command3 } from "commander";
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
const start = Date.now();
|
|
2093
|
-
let lastLog = 0;
|
|
2094
|
-
let attempt = 0;
|
|
2095
|
-
const backoffMs = (n) => Math.min(3e3 * Math.pow(2, n), 3e4);
|
|
2096
|
-
while (Date.now() - start < timeoutMs) {
|
|
2097
|
-
try {
|
|
2098
|
-
const { stdout, code } = await dockerExec("curl -sf http://localhost:4200/health");
|
|
2099
|
-
if (code === 0 && stdout.includes("ok")) return true;
|
|
2100
|
-
} catch {
|
|
2101
|
-
}
|
|
2102
|
-
const elapsed = Date.now() - start;
|
|
2103
|
-
if (elapsed - lastLog >= 3e4) {
|
|
2104
|
-
lastLog = elapsed;
|
|
2105
|
-
try {
|
|
2106
|
-
const { stdout } = await dockerExec("systemctl is-active nexus 2>/dev/null || echo 'starting...'");
|
|
2107
|
-
process.stderr.write(`
|
|
2108
|
-
[server ${Math.round(elapsed / 1e3)}s] ${stdout.trim().slice(0, 120)}
|
|
2109
|
-
`);
|
|
2110
|
-
} catch {
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
const delay = backoffMs(attempt++);
|
|
2114
|
-
const remaining = timeoutMs - (Date.now() - start);
|
|
2115
|
-
if (remaining <= 0) break;
|
|
2116
|
-
await new Promise((r) => setTimeout(r, Math.min(delay, remaining)));
|
|
2117
|
-
}
|
|
2118
|
-
return false;
|
|
2119
|
-
}
|
|
2120
|
-
|
|
2121
|
-
// src/commands/start.ts
|
|
2344
|
+
init_logger();
|
|
2345
|
+
init_secrets();
|
|
2346
|
+
init_docker();
|
|
2122
2347
|
var startCommand = new Command3("start").description("Start the NEXUS runtime").action(async () => {
|
|
2123
2348
|
const config = loadConfig();
|
|
2124
2349
|
if (!config) {
|
|
@@ -2133,16 +2358,17 @@ var startCommand = new Command3("start").description("Start the NEXUS runtime").
|
|
|
2133
2358
|
spinner.start();
|
|
2134
2359
|
await pullImage("buildwithnexus/nexus", "latest");
|
|
2135
2360
|
succeed(spinner, "Image ready");
|
|
2361
|
+
const keys = loadKeys();
|
|
2362
|
+
if (!keys) {
|
|
2363
|
+
log.error("No API keys found. Run: buildwithnexus init");
|
|
2364
|
+
process.exit(1);
|
|
2365
|
+
}
|
|
2136
2366
|
spinner = createSpinner("Starting NEXUS container...");
|
|
2137
2367
|
spinner.start();
|
|
2138
|
-
await
|
|
2139
|
-
{ anthropic:
|
|
2368
|
+
const ok = await launchNexus(
|
|
2369
|
+
{ anthropic: keys.ANTHROPIC_API_KEY, openai: keys.OPENAI_API_KEY || "" },
|
|
2140
2370
|
{ port: config.httpPort }
|
|
2141
2371
|
);
|
|
2142
|
-
succeed(spinner, "Container started");
|
|
2143
|
-
spinner = createSpinner("Waiting for NEXUS server...");
|
|
2144
|
-
spinner.start();
|
|
2145
|
-
const ok = await waitForServer(6e4);
|
|
2146
2372
|
if (ok) {
|
|
2147
2373
|
succeed(spinner, "NEXUS server running");
|
|
2148
2374
|
} else {
|
|
@@ -2163,12 +2389,11 @@ var startCommand = new Command3("start").description("Start the NEXUS runtime").
|
|
|
2163
2389
|
|
|
2164
2390
|
// src/commands/stop.ts
|
|
2165
2391
|
import { Command as Command4 } from "commander";
|
|
2166
|
-
import {
|
|
2167
|
-
|
|
2168
|
-
var execFileAsync = promisify(execFile);
|
|
2392
|
+
import { execa as execa3 } from "execa";
|
|
2393
|
+
init_logger();
|
|
2169
2394
|
async function containerExists() {
|
|
2170
2395
|
try {
|
|
2171
|
-
const { stdout } = await
|
|
2396
|
+
const { stdout } = await execa3("docker", [
|
|
2172
2397
|
"ps",
|
|
2173
2398
|
"-a",
|
|
2174
2399
|
"--filter",
|
|
@@ -2183,7 +2408,7 @@ async function containerExists() {
|
|
|
2183
2408
|
}
|
|
2184
2409
|
async function isContainerRunning() {
|
|
2185
2410
|
try {
|
|
2186
|
-
const { stdout } = await
|
|
2411
|
+
const { stdout } = await execa3("docker", [
|
|
2187
2412
|
"ps",
|
|
2188
2413
|
"--filter",
|
|
2189
2414
|
"name=^nexus$",
|
|
@@ -2207,10 +2432,10 @@ var stopCommand = new Command4("stop").description("Gracefully shut down the NEX
|
|
|
2207
2432
|
try {
|
|
2208
2433
|
if (await isContainerRunning()) {
|
|
2209
2434
|
spinner.text = "Stopping container...";
|
|
2210
|
-
await
|
|
2435
|
+
await execa3("docker", ["stop", "nexus"]);
|
|
2211
2436
|
}
|
|
2212
2437
|
spinner.text = "Removing container...";
|
|
2213
|
-
await
|
|
2438
|
+
await execa3("docker", ["rm", "nexus"]);
|
|
2214
2439
|
succeed(spinner, "NEXUS container stopped and removed");
|
|
2215
2440
|
} catch (err) {
|
|
2216
2441
|
fail(spinner, "Failed to stop NEXUS container");
|
|
@@ -2220,6 +2445,9 @@ var stopCommand = new Command4("stop").description("Gracefully shut down the NEX
|
|
|
2220
2445
|
});
|
|
2221
2446
|
|
|
2222
2447
|
// src/commands/status.ts
|
|
2448
|
+
init_logger();
|
|
2449
|
+
init_secrets();
|
|
2450
|
+
init_docker();
|
|
2223
2451
|
import { Command as Command5 } from "commander";
|
|
2224
2452
|
import chalk8 from "chalk";
|
|
2225
2453
|
async function checkHttpHealth(port) {
|
|
@@ -2294,10 +2522,14 @@ var statusCommand = new Command5("status").description("Check NEXUS runtime heal
|
|
|
2294
2522
|
});
|
|
2295
2523
|
|
|
2296
2524
|
// src/commands/doctor.ts
|
|
2525
|
+
init_logger();
|
|
2526
|
+
init_platform();
|
|
2527
|
+
init_docker();
|
|
2528
|
+
init_secrets();
|
|
2297
2529
|
import { Command as Command6 } from "commander";
|
|
2298
2530
|
import chalk9 from "chalk";
|
|
2299
|
-
import
|
|
2300
|
-
import
|
|
2531
|
+
import fs3 from "fs";
|
|
2532
|
+
import path5 from "path";
|
|
2301
2533
|
var doctorCommand = new Command6("doctor").description("Diagnose NEXUS runtime environment").action(async () => {
|
|
2302
2534
|
const platform = detectPlatform();
|
|
2303
2535
|
const check = (ok) => ok ? chalk9.green("\u2713") : chalk9.red("\u2717");
|
|
@@ -2313,7 +2545,7 @@ var doctorCommand = new Command6("doctor").description("Diagnose NEXUS runtime e
|
|
|
2313
2545
|
} else {
|
|
2314
2546
|
console.log(` ${check(false)} Docker not installed`);
|
|
2315
2547
|
}
|
|
2316
|
-
const keyExists =
|
|
2548
|
+
const keyExists = fs3.existsSync(path5.join(NEXUS_HOME2, "ssh", "id_nexus_vm"));
|
|
2317
2549
|
console.log(` ${check(keyExists)} SSH key`);
|
|
2318
2550
|
const config = loadConfig();
|
|
2319
2551
|
console.log(` ${check(!!config)} Configuration`);
|
|
@@ -2345,12 +2577,13 @@ var doctorCommand = new Command6("doctor").description("Diagnose NEXUS runtime e
|
|
|
2345
2577
|
});
|
|
2346
2578
|
|
|
2347
2579
|
// src/commands/logs.ts
|
|
2580
|
+
init_logger();
|
|
2348
2581
|
import { Command as Command7 } from "commander";
|
|
2349
|
-
import { execa as
|
|
2582
|
+
import { execa as execa4 } from "execa";
|
|
2350
2583
|
var logsCommand = new Command7("logs").description("View NEXUS server logs").action(async () => {
|
|
2351
2584
|
let containerExists2 = false;
|
|
2352
2585
|
try {
|
|
2353
|
-
const { stdout } = await
|
|
2586
|
+
const { stdout } = await execa4("docker", [
|
|
2354
2587
|
"ps",
|
|
2355
2588
|
"-a",
|
|
2356
2589
|
"--filter",
|
|
@@ -2367,7 +2600,7 @@ var logsCommand = new Command7("logs").description("View NEXUS server logs").act
|
|
|
2367
2600
|
log.error("NEXUS container not found. Start with: buildwithnexus start");
|
|
2368
2601
|
process.exit(1);
|
|
2369
2602
|
}
|
|
2370
|
-
const proc =
|
|
2603
|
+
const proc = execa4("docker", ["logs", "-f", "nexus"], {
|
|
2371
2604
|
stdout: "pipe",
|
|
2372
2605
|
stderr: "pipe"
|
|
2373
2606
|
});
|
|
@@ -2378,16 +2611,19 @@ var logsCommand = new Command7("logs").description("View NEXUS server logs").act
|
|
|
2378
2611
|
|
|
2379
2612
|
// src/commands/update.ts
|
|
2380
2613
|
import { Command as Command8 } from "commander";
|
|
2381
|
-
import
|
|
2382
|
-
import
|
|
2383
|
-
import { fileURLToPath as
|
|
2384
|
-
|
|
2614
|
+
import path6 from "path";
|
|
2615
|
+
import fs4 from "fs";
|
|
2616
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2617
|
+
init_logger();
|
|
2618
|
+
init_secrets();
|
|
2619
|
+
init_docker();
|
|
2620
|
+
import { execa as execa5 } from "execa";
|
|
2385
2621
|
function getReleaseTarball() {
|
|
2386
|
-
const dir =
|
|
2387
|
-
const tarballPath =
|
|
2388
|
-
if (
|
|
2389
|
-
const rootPath =
|
|
2390
|
-
if (
|
|
2622
|
+
const dir = path6.dirname(fileURLToPath2(import.meta.url));
|
|
2623
|
+
const tarballPath = path6.join(dir, "nexus-release.tar.gz");
|
|
2624
|
+
if (fs4.existsSync(tarballPath)) return tarballPath;
|
|
2625
|
+
const rootPath = path6.resolve(dir, "..", "dist", "nexus-release.tar.gz");
|
|
2626
|
+
if (fs4.existsSync(rootPath)) return rootPath;
|
|
2391
2627
|
throw new Error("nexus-release.tar.gz not found. Reinstall buildwithnexus to get the latest release.");
|
|
2392
2628
|
}
|
|
2393
2629
|
var updateCommand = new Command8("update").description("Update NEXUS to the latest bundled release and restart").action(async () => {
|
|
@@ -2403,7 +2639,7 @@ var updateCommand = new Command8("update").description("Update NEXUS to the late
|
|
|
2403
2639
|
let spinner = createSpinner("Uploading release tarball...");
|
|
2404
2640
|
spinner.start();
|
|
2405
2641
|
const tarballPath = getReleaseTarball();
|
|
2406
|
-
await
|
|
2642
|
+
await execa5("docker", ["cp", tarballPath, "nexus:/tmp/nexus-release.tar.gz"]);
|
|
2407
2643
|
succeed(spinner, "Tarball uploaded");
|
|
2408
2644
|
spinner = createSpinner("Stopping NEXUS server...");
|
|
2409
2645
|
spinner.start();
|
|
@@ -2438,9 +2674,12 @@ var updateCommand = new Command8("update").description("Update NEXUS to the late
|
|
|
2438
2674
|
// src/commands/destroy.ts
|
|
2439
2675
|
import { Command as Command9 } from "commander";
|
|
2440
2676
|
import chalk10 from "chalk";
|
|
2441
|
-
import
|
|
2677
|
+
import fs5 from "fs";
|
|
2442
2678
|
import { input } from "@inquirer/prompts";
|
|
2443
|
-
|
|
2679
|
+
init_logger();
|
|
2680
|
+
init_secrets();
|
|
2681
|
+
init_docker();
|
|
2682
|
+
import path7 from "path";
|
|
2444
2683
|
var destroyCommand = new Command9("destroy").description("Remove NEXUS VM and all data").option("--force", "Skip confirmation").action(async (opts) => {
|
|
2445
2684
|
const config = loadConfig();
|
|
2446
2685
|
if (!opts.force) {
|
|
@@ -2467,9 +2706,9 @@ var destroyCommand = new Command9("destroy").description("Remove NEXUS VM and al
|
|
|
2467
2706
|
}
|
|
2468
2707
|
await stopNexus();
|
|
2469
2708
|
}
|
|
2470
|
-
const sshConfigPath =
|
|
2471
|
-
if (
|
|
2472
|
-
const content =
|
|
2709
|
+
const sshConfigPath = path7.join(process.env.HOME || "~", ".ssh", "config");
|
|
2710
|
+
if (fs5.existsSync(sshConfigPath)) {
|
|
2711
|
+
const content = fs5.readFileSync(sshConfigPath, "utf-8");
|
|
2473
2712
|
const lines = content.split("\n");
|
|
2474
2713
|
const filtered = [];
|
|
2475
2714
|
let skip = false;
|
|
@@ -2482,14 +2721,17 @@ var destroyCommand = new Command9("destroy").description("Remove NEXUS VM and al
|
|
|
2482
2721
|
skip = false;
|
|
2483
2722
|
filtered.push(line);
|
|
2484
2723
|
}
|
|
2485
|
-
|
|
2724
|
+
fs5.writeFileSync(sshConfigPath, filtered.join("\n"));
|
|
2486
2725
|
}
|
|
2487
|
-
|
|
2726
|
+
fs5.rmSync(NEXUS_HOME2, { recursive: true, force: true });
|
|
2488
2727
|
succeed(spinner, "NEXUS runtime destroyed");
|
|
2489
2728
|
log.dim("Run 'buildwithnexus init' to set up again");
|
|
2490
2729
|
});
|
|
2491
2730
|
|
|
2492
2731
|
// src/commands/keys.ts
|
|
2732
|
+
init_logger();
|
|
2733
|
+
init_secrets();
|
|
2734
|
+
init_dlp();
|
|
2493
2735
|
import { Command as Command10 } from "commander";
|
|
2494
2736
|
import { password as password2 } from "@inquirer/prompts";
|
|
2495
2737
|
import chalk11 from "chalk";
|
|
@@ -2548,8 +2790,10 @@ keysCommand.command("set <key>").description("Set or update an API key (e.g. ANT
|
|
|
2548
2790
|
});
|
|
2549
2791
|
|
|
2550
2792
|
// src/commands/ssh.ts
|
|
2793
|
+
init_logger();
|
|
2794
|
+
init_docker();
|
|
2551
2795
|
import { Command as Command11 } from "commander";
|
|
2552
|
-
import { execa as
|
|
2796
|
+
import { execa as execa6 } from "execa";
|
|
2553
2797
|
var sshCommand = new Command11("ssh").description("Open an interactive shell inside the NEXUS container").action(async () => {
|
|
2554
2798
|
const running = await isNexusRunning();
|
|
2555
2799
|
if (!running) {
|
|
@@ -2557,13 +2801,17 @@ var sshCommand = new Command11("ssh").description("Open an interactive shell ins
|
|
|
2557
2801
|
process.exit(1);
|
|
2558
2802
|
}
|
|
2559
2803
|
log.dim("Opening shell in NEXUS container...");
|
|
2560
|
-
await
|
|
2804
|
+
await execa6("docker", ["exec", "-it", "nexus", "/bin/bash"], { stdio: "inherit" });
|
|
2561
2805
|
});
|
|
2562
2806
|
|
|
2563
2807
|
// src/commands/brainstorm.ts
|
|
2808
|
+
init_logger();
|
|
2564
2809
|
import { Command as Command12 } from "commander";
|
|
2565
2810
|
import chalk12 from "chalk";
|
|
2566
2811
|
import { input as input2 } from "@inquirer/prompts";
|
|
2812
|
+
init_secrets();
|
|
2813
|
+
init_docker();
|
|
2814
|
+
init_dlp();
|
|
2567
2815
|
var COS_PREFIX = chalk12.bold.cyan(" Chief of Staff");
|
|
2568
2816
|
var YOU_PREFIX = chalk12.bold.white(" You");
|
|
2569
2817
|
var DIVIDER = chalk12.dim(" " + "\u2500".repeat(56));
|
|
@@ -2601,13 +2849,7 @@ var brainstormCommand = new Command12("brainstorm").description("Brainstorm an i
|
|
|
2601
2849
|
}
|
|
2602
2850
|
const spinner = createSpinner("Connecting to NEXUS...");
|
|
2603
2851
|
spinner.start();
|
|
2604
|
-
|
|
2605
|
-
try {
|
|
2606
|
-
const healthRes = await fetch(`http://localhost:${config.httpPort}/health`);
|
|
2607
|
-
const healthText = await healthRes.text();
|
|
2608
|
-
healthOk = healthRes.ok && healthText.includes("ok");
|
|
2609
|
-
} catch {
|
|
2610
|
-
}
|
|
2852
|
+
const healthOk = await checkServerHealth(config.httpPort);
|
|
2611
2853
|
if (!healthOk) {
|
|
2612
2854
|
fail(spinner, "NEXUS server is not healthy");
|
|
2613
2855
|
log.warn("Check status: buildwithnexus status");
|
|
@@ -2682,9 +2924,13 @@ var brainstormCommand = new Command12("brainstorm").description("Brainstorm an i
|
|
|
2682
2924
|
});
|
|
2683
2925
|
|
|
2684
2926
|
// src/commands/ninety-nine.ts
|
|
2927
|
+
init_logger();
|
|
2685
2928
|
import { Command as Command13 } from "commander";
|
|
2686
2929
|
import chalk13 from "chalk";
|
|
2687
2930
|
import { input as input3 } from "@inquirer/prompts";
|
|
2931
|
+
init_secrets();
|
|
2932
|
+
init_docker();
|
|
2933
|
+
init_dlp();
|
|
2688
2934
|
var AGENT_PREFIX = chalk13.bold.green(" 99 \u276F");
|
|
2689
2935
|
var YOU_PREFIX2 = chalk13.bold.white(" You");
|
|
2690
2936
|
var DIVIDER2 = chalk13.dim(" " + "\u2500".repeat(56));
|
|
@@ -2872,15 +3118,20 @@ var ninetyNineCommand = new Command13("99").description("AI pair-programming ses
|
|
|
2872
3118
|
});
|
|
2873
3119
|
|
|
2874
3120
|
// src/commands/shell.ts
|
|
3121
|
+
init_logger();
|
|
2875
3122
|
import { Command as Command14 } from "commander";
|
|
2876
3123
|
import chalk16 from "chalk";
|
|
3124
|
+
init_secrets();
|
|
3125
|
+
init_docker();
|
|
3126
|
+
init_dlp();
|
|
2877
3127
|
|
|
2878
3128
|
// src/ui/repl.ts
|
|
3129
|
+
init_secrets();
|
|
2879
3130
|
import readline3 from "readline";
|
|
2880
|
-
import
|
|
2881
|
-
import
|
|
3131
|
+
import fs6 from "fs";
|
|
3132
|
+
import path8 from "path";
|
|
2882
3133
|
import chalk14 from "chalk";
|
|
2883
|
-
var HISTORY_FILE =
|
|
3134
|
+
var HISTORY_FILE = path8.join(NEXUS_HOME2, "shell_history");
|
|
2884
3135
|
var MAX_HISTORY = 1e3;
|
|
2885
3136
|
var Repl = class {
|
|
2886
3137
|
rl = null;
|
|
@@ -2896,17 +3147,17 @@ var Repl = class {
|
|
|
2896
3147
|
}
|
|
2897
3148
|
loadHistory() {
|
|
2898
3149
|
try {
|
|
2899
|
-
if (
|
|
2900
|
-
this.history =
|
|
3150
|
+
if (fs6.existsSync(HISTORY_FILE)) {
|
|
3151
|
+
this.history = fs6.readFileSync(HISTORY_FILE, "utf-8").split("\n").filter(Boolean).slice(-MAX_HISTORY);
|
|
2901
3152
|
}
|
|
2902
3153
|
} catch {
|
|
2903
3154
|
}
|
|
2904
3155
|
}
|
|
2905
3156
|
saveHistory() {
|
|
2906
3157
|
try {
|
|
2907
|
-
const dir =
|
|
2908
|
-
|
|
2909
|
-
|
|
3158
|
+
const dir = path8.dirname(HISTORY_FILE);
|
|
3159
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
3160
|
+
fs6.writeFileSync(HISTORY_FILE, this.history.slice(-MAX_HISTORY).join("\n") + "\n", { mode: 384 });
|
|
2910
3161
|
} catch {
|
|
2911
3162
|
}
|
|
2912
3163
|
}
|
|
@@ -3002,6 +3253,9 @@ var Repl = class {
|
|
|
3002
3253
|
};
|
|
3003
3254
|
|
|
3004
3255
|
// src/core/event-stream.ts
|
|
3256
|
+
init_docker();
|
|
3257
|
+
init_secrets();
|
|
3258
|
+
init_dlp();
|
|
3005
3259
|
import chalk15 from "chalk";
|
|
3006
3260
|
var ROLE_COLORS = {
|
|
3007
3261
|
"Chief of Staff": chalk15.bold.cyan,
|
|
@@ -3116,33 +3370,6 @@ var EventStream = class {
|
|
|
3116
3370
|
};
|
|
3117
3371
|
|
|
3118
3372
|
// src/commands/shell.ts
|
|
3119
|
-
async function httpPost(httpPort, path12, body) {
|
|
3120
|
-
const res = await fetch(`http://localhost:${httpPort}${path12}`, {
|
|
3121
|
-
method: "POST",
|
|
3122
|
-
headers: { "Content-Type": "application/json" },
|
|
3123
|
-
body: JSON.stringify(body),
|
|
3124
|
-
signal: AbortSignal.timeout(6e4)
|
|
3125
|
-
});
|
|
3126
|
-
if (!res.ok) throw new Error(`Server returned ${res.status}`);
|
|
3127
|
-
const text = await res.text();
|
|
3128
|
-
try {
|
|
3129
|
-
const parsed = JSON.parse(text);
|
|
3130
|
-
return parsed.response ?? parsed.message ?? text;
|
|
3131
|
-
} catch {
|
|
3132
|
-
return text;
|
|
3133
|
-
}
|
|
3134
|
-
}
|
|
3135
|
-
async function httpGet(httpPort, path12) {
|
|
3136
|
-
try {
|
|
3137
|
-
const res = await fetch(`http://localhost:${httpPort}${path12}`, {
|
|
3138
|
-
signal: AbortSignal.timeout(1e4)
|
|
3139
|
-
});
|
|
3140
|
-
const text = await res.text();
|
|
3141
|
-
return { ok: res.ok, text };
|
|
3142
|
-
} catch {
|
|
3143
|
-
return { ok: false, text: "" };
|
|
3144
|
-
}
|
|
3145
|
-
}
|
|
3146
3373
|
async function sendMessage2(httpPort, message) {
|
|
3147
3374
|
return httpPost(httpPort, "/message", { message, source: "shell" });
|
|
3148
3375
|
}
|
|
@@ -3376,12 +3603,12 @@ var shellCommand2 = new Command14("shell").description("Launch the interactive N
|
|
|
3376
3603
|
name: "logs",
|
|
3377
3604
|
description: "Show recent container logs",
|
|
3378
3605
|
handler: async () => {
|
|
3379
|
-
const { execa:
|
|
3606
|
+
const { execa: execa7 } = await import("execa");
|
|
3380
3607
|
console.log("");
|
|
3381
3608
|
console.log(chalk16.bold(" Recent Logs:"));
|
|
3382
3609
|
console.log(chalk16.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"));
|
|
3383
3610
|
try {
|
|
3384
|
-
const { stdout } = await
|
|
3611
|
+
const { stdout } = await execa7("docker", ["logs", "--tail", "30", "nexus"]);
|
|
3385
3612
|
for (const line of redact(stdout).split("\n")) {
|
|
3386
3613
|
console.log(chalk16.dim(" " + line));
|
|
3387
3614
|
}
|
|
@@ -3395,10 +3622,10 @@ var shellCommand2 = new Command14("shell").description("Launch the interactive N
|
|
|
3395
3622
|
name: "exec",
|
|
3396
3623
|
description: "Drop into the container shell for debugging/inspection",
|
|
3397
3624
|
handler: async () => {
|
|
3398
|
-
const { execa:
|
|
3625
|
+
const { execa: execa7 } = await import("execa");
|
|
3399
3626
|
eventStream.stop();
|
|
3400
3627
|
try {
|
|
3401
|
-
await
|
|
3628
|
+
await execa7("docker", ["exec", "-it", "nexus", "/bin/sh"], { stdio: "inherit" });
|
|
3402
3629
|
} catch {
|
|
3403
3630
|
}
|
|
3404
3631
|
eventStream.start();
|
|
@@ -3551,49 +3778,19 @@ var shellCommand2 = new Command14("shell").description("Launch the interactive N
|
|
|
3551
3778
|
}
|
|
3552
3779
|
});
|
|
3553
3780
|
|
|
3554
|
-
// src/cli.ts
|
|
3555
|
-
function getVersionStatic() {
|
|
3556
|
-
try {
|
|
3557
|
-
const __dirname2 = dirname2(fileURLToPath4(import.meta.url));
|
|
3558
|
-
const packagePath = join2(__dirname2, "..", "package.json");
|
|
3559
|
-
const packageJson = JSON.parse(readFileSync2(packagePath, "utf-8"));
|
|
3560
|
-
return packageJson.version;
|
|
3561
|
-
} catch {
|
|
3562
|
-
return true ? "0.7.1" : "0.0.0-unknown";
|
|
3563
|
-
}
|
|
3564
|
-
}
|
|
3565
|
-
var cli = new Command15().name("buildwithnexus").description("Auto-scaffold and launch a fully autonomous NEXUS runtime").version(getVersionStatic());
|
|
3566
|
-
cli.addCommand(installCommand);
|
|
3567
|
-
cli.addCommand(initCommand);
|
|
3568
|
-
cli.addCommand(startCommand);
|
|
3569
|
-
cli.addCommand(stopCommand);
|
|
3570
|
-
cli.addCommand(statusCommand);
|
|
3571
|
-
cli.addCommand(doctorCommand);
|
|
3572
|
-
cli.addCommand(logsCommand);
|
|
3573
|
-
cli.addCommand(updateCommand);
|
|
3574
|
-
cli.addCommand(destroyCommand);
|
|
3575
|
-
cli.addCommand(keysCommand);
|
|
3576
|
-
cli.addCommand(sshCommand);
|
|
3577
|
-
cli.addCommand(brainstormCommand);
|
|
3578
|
-
cli.addCommand(ninetyNineCommand);
|
|
3579
|
-
cli.addCommand(shellCommand2);
|
|
3580
|
-
cli.action(() => {
|
|
3581
|
-
cli.help();
|
|
3582
|
-
});
|
|
3583
|
-
|
|
3584
3781
|
// src/core/update-notifier.ts
|
|
3585
|
-
import
|
|
3586
|
-
import
|
|
3587
|
-
import
|
|
3782
|
+
import fs7 from "fs";
|
|
3783
|
+
import path9 from "path";
|
|
3784
|
+
import os3 from "os";
|
|
3588
3785
|
import https from "https";
|
|
3589
3786
|
import chalk17 from "chalk";
|
|
3590
3787
|
var PACKAGE_NAME = "buildwithnexus";
|
|
3591
3788
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
3592
|
-
var STATE_DIR =
|
|
3593
|
-
var STATE_FILE =
|
|
3789
|
+
var STATE_DIR = path9.join(os3.homedir(), ".buildwithnexus");
|
|
3790
|
+
var STATE_FILE = path9.join(STATE_DIR, ".update-check.json");
|
|
3594
3791
|
function readState() {
|
|
3595
3792
|
try {
|
|
3596
|
-
const raw =
|
|
3793
|
+
const raw = fs7.readFileSync(STATE_FILE, "utf-8");
|
|
3597
3794
|
return JSON.parse(raw);
|
|
3598
3795
|
} catch {
|
|
3599
3796
|
return { lastCheck: 0, latestVersion: null };
|
|
@@ -3601,8 +3798,8 @@ function readState() {
|
|
|
3601
3798
|
}
|
|
3602
3799
|
function writeState(state) {
|
|
3603
3800
|
try {
|
|
3604
|
-
|
|
3605
|
-
|
|
3801
|
+
fs7.mkdirSync(STATE_DIR, { recursive: true, mode: 448 });
|
|
3802
|
+
fs7.writeFileSync(STATE_FILE, JSON.stringify(state), { mode: 384 });
|
|
3606
3803
|
} catch {
|
|
3607
3804
|
}
|
|
3608
3805
|
}
|
|
@@ -3674,18 +3871,34 @@ function printUpdateBanner(current, latest) {
|
|
|
3674
3871
|
process.stderr.write(msg + "\n");
|
|
3675
3872
|
}
|
|
3676
3873
|
|
|
3874
|
+
// src/core/models.ts
|
|
3875
|
+
var MODELS = {
|
|
3876
|
+
OPUS: "claude-opus-4-6",
|
|
3877
|
+
SONNET: "claude-sonnet-4-6-20250929",
|
|
3878
|
+
HAIKU: "claude-haiku-4-5-20251001",
|
|
3879
|
+
DEFAULT: "claude-sonnet-4-6-20250929"
|
|
3880
|
+
};
|
|
3881
|
+
|
|
3677
3882
|
// src/bin.ts
|
|
3678
3883
|
import dotenv2 from "dotenv";
|
|
3679
|
-
import
|
|
3680
|
-
import
|
|
3681
|
-
var homeEnvPath =
|
|
3884
|
+
import os4 from "os";
|
|
3885
|
+
import path10 from "path";
|
|
3886
|
+
var homeEnvPath = path10.join(os4.homedir(), ".env.local");
|
|
3682
3887
|
dotenv2.config({ path: homeEnvPath });
|
|
3683
|
-
var version =
|
|
3888
|
+
var version = resolvedVersion;
|
|
3684
3889
|
checkForUpdates(version);
|
|
3685
3890
|
program.name("buildwithnexus").description("Nexus - AI-Powered Task Execution").version(version);
|
|
3686
3891
|
program.command("da-init").description("Initialize Nexus (set up API keys and .env.local)").action(deepAgentsInitCommand);
|
|
3687
|
-
program.command("run <task>").description("Run a task with Nexus").option("-a, --agent <name>", "Agent role (engineer, researcher, etc)", "engineer").option("-g, --goal <goal>", "Agent goal").option("-m, --model <model>", "LLM model",
|
|
3892
|
+
program.command("run <task>").description("Run a task with Nexus").option("-a, --agent <name>", "Agent role (engineer, researcher, etc)", "engineer").option("-g, --goal <goal>", "Agent goal").option("-m, --model <model>", "LLM model", MODELS.DEFAULT).action(runCommand);
|
|
3688
3893
|
program.command("dashboard").description("Start the Nexus dashboard server").option("-p, --port <port>", "Dashboard port", "4201").action(dashboardCommand);
|
|
3894
|
+
program.command("server").description("Start the Nexus backend server").action(async () => {
|
|
3895
|
+
const { startBackend: startBackend2 } = await Promise.resolve().then(() => (init_docker(), docker_exports));
|
|
3896
|
+
await startBackend2();
|
|
3897
|
+
const chalk18 = (await import("chalk")).default;
|
|
3898
|
+
console.log(chalk18.green("Backend server started. Press Ctrl+C to stop."));
|
|
3899
|
+
await new Promise(() => {
|
|
3900
|
+
});
|
|
3901
|
+
});
|
|
3689
3902
|
program.command("da-status").description("Check Nexus backend status").action(async () => {
|
|
3690
3903
|
const backendUrl = process.env.BACKEND_URL || "http://localhost:4200";
|
|
3691
3904
|
try {
|
|
@@ -3703,12 +3916,20 @@ program.command("da-status").description("Check Nexus backend status").action(as
|
|
|
3703
3916
|
console.log(" cd ~/Projects/nexus && python -m src.deep_agents_server");
|
|
3704
3917
|
}
|
|
3705
3918
|
});
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3919
|
+
program.addCommand(installCommand);
|
|
3920
|
+
program.addCommand(initCommand);
|
|
3921
|
+
program.addCommand(startCommand);
|
|
3922
|
+
program.addCommand(stopCommand);
|
|
3923
|
+
program.addCommand(statusCommand);
|
|
3924
|
+
program.addCommand(doctorCommand);
|
|
3925
|
+
program.addCommand(logsCommand);
|
|
3926
|
+
program.addCommand(updateCommand);
|
|
3927
|
+
program.addCommand(destroyCommand);
|
|
3928
|
+
program.addCommand(keysCommand);
|
|
3929
|
+
program.addCommand(sshCommand);
|
|
3930
|
+
program.addCommand(brainstormCommand);
|
|
3931
|
+
program.addCommand(ninetyNineCommand);
|
|
3932
|
+
program.addCommand(shellCommand2);
|
|
3712
3933
|
if (!process.argv.slice(2).length) {
|
|
3713
3934
|
interactiveMode().catch((err) => {
|
|
3714
3935
|
console.error(err);
|
|
@@ -3717,3 +3938,6 @@ if (!process.argv.slice(2).length) {
|
|
|
3717
3938
|
} else {
|
|
3718
3939
|
program.parse();
|
|
3719
3940
|
}
|
|
3941
|
+
export {
|
|
3942
|
+
version
|
|
3943
|
+
};
|