buildwithnexus 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1830 -1646
- package/dist/deep-agents-bin.js +29 -6
- 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,1491 +226,1637 @@ 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
|
-
console.log(chalk5.dim(` v${getVersion()} \xB7 buildwithnexus.dev
|
|
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"));
|
|
203
|
-
}
|
|
204
|
-
lines.push(
|
|
205
|
-
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"),
|
|
206
|
-
chalk5.green(" \u2551 ") + chalk5.dim("Quick Start:".padEnd(55)) + chalk5.green("\u2551"),
|
|
207
|
-
chalk5.green(" \u2551 ") + chalk5.white(" buildwithnexus - Interactive shell".padEnd(55)) + chalk5.green("\u2551"),
|
|
208
|
-
chalk5.green(" \u2551 ") + chalk5.white(" buildwithnexus brainstorm - Brainstorm ideas".padEnd(55)) + chalk5.green("\u2551"),
|
|
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"));
|
|
217
|
-
}
|
|
218
|
-
var BANNER;
|
|
219
|
-
var init_banner = __esm({
|
|
220
|
-
"src/ui/banner.ts"() {
|
|
425
|
+
var init_platform = __esm({
|
|
426
|
+
"src/core/platform.ts"() {
|
|
221
427
|
"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
428
|
}
|
|
230
429
|
});
|
|
231
430
|
|
|
232
|
-
// src/
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
done: colors.success("\u2714"),
|
|
254
|
-
active: colors.info("\u25C9"),
|
|
255
|
-
pending: colors.muted("\u25CB"),
|
|
256
|
-
error: colors.error("\u2716")
|
|
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();
|
|
280
|
-
}
|
|
281
|
-
displayConnecting() {
|
|
282
|
-
console.log(`${this.getSpinner()} ${colors.warning("Connecting to backend...")}`);
|
|
431
|
+
// src/core/docker.ts
|
|
432
|
+
var docker_exports = {};
|
|
433
|
+
__export(docker_exports, {
|
|
434
|
+
dockerExec: () => dockerExec,
|
|
435
|
+
imageExistsLocally: () => imageExistsLocally,
|
|
436
|
+
installDocker: () => installDocker,
|
|
437
|
+
isDockerInstalled: () => isDockerInstalled,
|
|
438
|
+
isNexusRunning: () => isNexusRunning,
|
|
439
|
+
pullImage: () => pullImage,
|
|
440
|
+
startBackend: () => startBackend,
|
|
441
|
+
startNexus: () => startNexus,
|
|
442
|
+
stopNexus: () => stopNexus
|
|
443
|
+
});
|
|
444
|
+
import { existsSync } from "fs";
|
|
445
|
+
import { execa } from "execa";
|
|
446
|
+
async function isDockerInstalled() {
|
|
447
|
+
try {
|
|
448
|
+
await execa("docker", ["info"]);
|
|
449
|
+
return true;
|
|
450
|
+
} catch {
|
|
451
|
+
return false;
|
|
283
452
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
453
|
+
}
|
|
454
|
+
async function isDockerInstalledButNotRunning() {
|
|
455
|
+
try {
|
|
456
|
+
await execa("docker", ["--version"]);
|
|
457
|
+
return true;
|
|
458
|
+
} catch {
|
|
459
|
+
return false;
|
|
287
460
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
461
|
+
}
|
|
462
|
+
function dockerDesktopExists() {
|
|
463
|
+
return existsSync(DOCKER_DESKTOP_APP_PATH);
|
|
464
|
+
}
|
|
465
|
+
async function ensureHomebrew() {
|
|
466
|
+
try {
|
|
467
|
+
await execa("which", ["brew"]);
|
|
468
|
+
log.dim("Homebrew is already installed.");
|
|
469
|
+
return;
|
|
470
|
+
} catch {
|
|
291
471
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
472
|
+
log.step("Installing Homebrew...");
|
|
473
|
+
try {
|
|
474
|
+
await execa("/bin/bash", [
|
|
475
|
+
"-c",
|
|
476
|
+
"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
477
|
+
], {
|
|
478
|
+
stdio: "inherit",
|
|
479
|
+
env: { ...process.env, NONINTERACTIVE: "1" }
|
|
298
480
|
});
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
displayEvent(type, data) {
|
|
304
|
-
this.eventCount++;
|
|
305
|
-
const content = data["content"] || "";
|
|
306
|
-
if (type === "agent_working") {
|
|
307
|
-
const agent = data["agent"] || "Agent";
|
|
308
|
-
const agentTask = data["task"] || "";
|
|
309
|
-
console.log("");
|
|
310
|
-
console.log(` ${colors.info("\u{1F464}")} ${colors.info(chalk.bold(agent))}`);
|
|
311
|
-
console.log(` ${colors.muted("\u2192")} ${agentTask}`);
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
if (type === "agent_result") {
|
|
315
|
-
const result = data["result"] || "";
|
|
316
|
-
let displayResult = result;
|
|
317
|
-
if (displayResult.length > 100) {
|
|
318
|
-
displayResult = displayResult.substring(0, 97) + "...";
|
|
319
|
-
}
|
|
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
|
-
}
|
|
337
|
-
console.log(` ${config.icon} ${config.color(displayContent)}`);
|
|
338
|
-
}
|
|
339
|
-
displayResults(summary, todosCompleted) {
|
|
340
|
-
console.log("");
|
|
341
|
-
console.log(colors.success("\u2501".repeat(60)));
|
|
342
|
-
console.log(colors.success.bold("\u2728 Execution Complete"));
|
|
343
|
-
console.log("");
|
|
344
|
-
const lines = summary.split("\n");
|
|
345
|
-
for (const line of lines) {
|
|
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("");
|
|
351
|
-
}
|
|
352
|
-
displayError(error) {
|
|
353
|
-
console.log("");
|
|
354
|
-
console.log(colors.error("\u274C Error"));
|
|
355
|
-
console.log(colors.error(error));
|
|
356
|
-
console.log("");
|
|
357
|
-
}
|
|
358
|
-
displayComplete(duration) {
|
|
359
|
-
const seconds = (duration / 1e3).toFixed(1);
|
|
360
|
-
console.log("");
|
|
361
|
-
console.log(colors.success.bold(`\u2728 Complete in ${seconds}s`));
|
|
362
|
-
console.log(colors.muted(`${this.eventCount} event(s) streamed`));
|
|
363
|
-
console.log("");
|
|
481
|
+
} catch {
|
|
482
|
+
throw new Error(
|
|
483
|
+
'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'
|
|
484
|
+
);
|
|
364
485
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
console.log("");
|
|
486
|
+
try {
|
|
487
|
+
await execa("which", ["brew"]);
|
|
488
|
+
} catch {
|
|
489
|
+
const armPath = "/opt/homebrew/bin";
|
|
490
|
+
const intelPath = "/usr/local/bin";
|
|
491
|
+
process.env.PATH = `${armPath}:${intelPath}:${process.env.PATH}`;
|
|
492
|
+
log.dim("Added Homebrew paths to PATH for this session.");
|
|
374
493
|
}
|
|
375
|
-
|
|
376
|
-
|
|
494
|
+
try {
|
|
495
|
+
await execa("brew", ["--version"]);
|
|
496
|
+
log.success("Homebrew installed successfully.");
|
|
497
|
+
} catch {
|
|
498
|
+
throw new Error(
|
|
499
|
+
"Homebrew was installed but is not available on PATH.\n\n Try opening a new terminal and re-running:\n buildwithnexus init"
|
|
500
|
+
);
|
|
377
501
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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);
|
|
392
|
-
});
|
|
393
|
-
console.log(parts.join(colors.muted(" \u2022 ")));
|
|
394
|
-
console.log(colors.muted("[s] switch mode"));
|
|
395
|
-
console.log("");
|
|
396
|
-
}
|
|
397
|
-
displayModeHeader(mode) {
|
|
398
|
-
const config = {
|
|
399
|
-
PLAN: {
|
|
400
|
-
icon: "\u{1F4CB}",
|
|
401
|
-
desc: "Break down and review steps",
|
|
402
|
-
color: colors.info
|
|
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
|
|
502
|
+
}
|
|
503
|
+
async function installDocker(platform) {
|
|
504
|
+
const p = platform ?? detectPlatform();
|
|
505
|
+
switch (p.os) {
|
|
506
|
+
case "mac": {
|
|
507
|
+
if (await isDockerInstalled()) {
|
|
508
|
+
log.success("Docker is already running.");
|
|
509
|
+
return;
|
|
413
510
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
511
|
+
if (dockerDesktopExists()) {
|
|
512
|
+
log.step(`Docker Desktop found at ${DOCKER_DESKTOP_APP_PATH} but not running. Attempting to start...`);
|
|
513
|
+
let launched = false;
|
|
514
|
+
log.dim(`Trying: open ${DOCKER_DESKTOP_APP_PATH}`);
|
|
515
|
+
try {
|
|
516
|
+
await execa("open", [DOCKER_DESKTOP_APP_PATH]);
|
|
517
|
+
launched = true;
|
|
518
|
+
log.dim(`Launch command succeeded via ${DOCKER_DESKTOP_APP_PATH}`);
|
|
519
|
+
} catch {
|
|
520
|
+
log.warn(`Could not launch via ${DOCKER_DESKTOP_APP_PATH} \u2014 trying fallback...`);
|
|
521
|
+
}
|
|
522
|
+
if (!launched) {
|
|
523
|
+
log.dim("Trying: open -a Docker");
|
|
524
|
+
try {
|
|
525
|
+
await execa("open", ["-a", "Docker"]);
|
|
526
|
+
launched = true;
|
|
527
|
+
log.dim("Launch command succeeded via open -a Docker");
|
|
528
|
+
} catch {
|
|
529
|
+
log.warn("Both launch attempts failed.");
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (launched) {
|
|
533
|
+
log.step("Docker Desktop is starting up. Waiting for the daemon to be ready (up to 120s)...");
|
|
534
|
+
try {
|
|
535
|
+
await waitForDockerDaemon(12e4);
|
|
536
|
+
return;
|
|
537
|
+
} catch {
|
|
538
|
+
log.warn("Docker Desktop was launched but the daemon did not become ready in time.");
|
|
539
|
+
}
|
|
540
|
+
} else {
|
|
541
|
+
log.warn("Could not launch Docker Desktop. Will fall back to reinstalling via Homebrew.");
|
|
542
|
+
}
|
|
441
543
|
} else {
|
|
442
|
-
|
|
544
|
+
log.step(`Docker Desktop not found at ${DOCKER_DESKTOP_APP_PATH}.`);
|
|
443
545
|
}
|
|
546
|
+
log.step("Installing Docker Desktop via Homebrew...");
|
|
547
|
+
await ensureHomebrew();
|
|
548
|
+
try {
|
|
549
|
+
await execa("brew", ["install", "--cask", "docker"], {
|
|
550
|
+
stdio: "inherit",
|
|
551
|
+
timeout: 6e4
|
|
552
|
+
});
|
|
553
|
+
} catch (err) {
|
|
554
|
+
const e = err;
|
|
555
|
+
if (e.killed && e.signal === "SIGINT") {
|
|
556
|
+
throw new Error("Docker installation cancelled by user (Ctrl+C)");
|
|
557
|
+
}
|
|
558
|
+
if (e.timedOut) {
|
|
559
|
+
throw new Error(
|
|
560
|
+
"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"
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
throw new Error(
|
|
564
|
+
"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"
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
log.step("Launching Docker Desktop...");
|
|
568
|
+
let postInstallLaunched = false;
|
|
569
|
+
log.dim(`Trying: open ${DOCKER_DESKTOP_APP_PATH}`);
|
|
570
|
+
try {
|
|
571
|
+
await execa("open", [DOCKER_DESKTOP_APP_PATH]);
|
|
572
|
+
postInstallLaunched = true;
|
|
573
|
+
log.dim(`Launch command succeeded via ${DOCKER_DESKTOP_APP_PATH}`);
|
|
574
|
+
} catch {
|
|
575
|
+
log.warn(`Could not launch via ${DOCKER_DESKTOP_APP_PATH} \u2014 trying fallback...`);
|
|
576
|
+
}
|
|
577
|
+
if (!postInstallLaunched) {
|
|
578
|
+
log.dim("Trying: open -a Docker");
|
|
579
|
+
try {
|
|
580
|
+
await execa("open", ["-a", "Docker"]);
|
|
581
|
+
postInstallLaunched = true;
|
|
582
|
+
log.dim("Launch command succeeded via open -a Docker");
|
|
583
|
+
} catch {
|
|
584
|
+
log.warn("Both launch attempts failed after install.");
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (!postInstallLaunched) {
|
|
588
|
+
throw new Error(
|
|
589
|
+
"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"
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
log.step("Docker Desktop is starting up. Waiting for the daemon to be ready (up to 120s)...");
|
|
593
|
+
await waitForDockerDaemon(12e4);
|
|
594
|
+
break;
|
|
444
595
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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);
|
|
596
|
+
case "linux": {
|
|
597
|
+
const linuxBinaryExists = await isDockerInstalledButNotRunning();
|
|
598
|
+
if (linuxBinaryExists) {
|
|
599
|
+
log.step("Docker is installed but the daemon is not running.");
|
|
600
|
+
log.step("Starting Docker daemon...");
|
|
601
|
+
try {
|
|
602
|
+
await execa("sudo", ["systemctl", "start", "docker"], { stdio: "inherit" });
|
|
603
|
+
log.dim("Started Docker daemon via systemctl.");
|
|
604
|
+
} catch {
|
|
605
|
+
try {
|
|
606
|
+
await execa("sudo", ["service", "docker", "start"], { stdio: "inherit" });
|
|
607
|
+
log.dim("Started Docker daemon via service command.");
|
|
608
|
+
} catch {
|
|
609
|
+
throw new Error(
|
|
610
|
+
"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"
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
log.step("Waiting for Docker...");
|
|
615
|
+
await waitForDockerDaemon(3e4);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
log.step("Installing Docker...");
|
|
619
|
+
log.warn("This may require your sudo password.");
|
|
620
|
+
log.dim("Running official Docker install script from https://get.docker.com ...");
|
|
621
|
+
try {
|
|
622
|
+
const { stdout: script } = await execa("curl", ["-fsSL", "https://get.docker.com"]);
|
|
623
|
+
await execa("sudo", ["sh", "-c", script], { stdio: "inherit" });
|
|
624
|
+
log.success("Docker installed successfully.");
|
|
625
|
+
} catch {
|
|
626
|
+
throw new Error(
|
|
627
|
+
"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"
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
log.dim("Adding current user to docker group...");
|
|
631
|
+
try {
|
|
632
|
+
const user = (await execa("whoami")).stdout.trim();
|
|
633
|
+
await execa("sudo", ["usermod", "-aG", "docker", user]);
|
|
634
|
+
log.dim(`Added user '${user}' to docker group (may require re-login for effect).`);
|
|
635
|
+
} catch {
|
|
636
|
+
log.warn("Could not add user to docker group. You may need sudo for docker commands.");
|
|
637
|
+
}
|
|
638
|
+
log.step("Starting Docker daemon...");
|
|
639
|
+
try {
|
|
640
|
+
await execa("sudo", ["systemctl", "start", "docker"], { stdio: "inherit" });
|
|
641
|
+
log.dim("Started Docker daemon via systemctl.");
|
|
642
|
+
} catch {
|
|
643
|
+
try {
|
|
644
|
+
await execa("sudo", ["service", "docker", "start"], { stdio: "inherit" });
|
|
645
|
+
log.dim("Started Docker daemon via service command.");
|
|
646
|
+
} catch {
|
|
647
|
+
throw new Error(
|
|
648
|
+
"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"
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
log.step("Waiting for Docker...");
|
|
653
|
+
await waitForDockerDaemon(3e4);
|
|
654
|
+
break;
|
|
513
655
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
656
|
+
case "windows": {
|
|
657
|
+
const winBinaryExists = await isDockerInstalledButNotRunning();
|
|
658
|
+
if (winBinaryExists) {
|
|
659
|
+
log.step("Docker Desktop is installed but not running. Attempting to start...");
|
|
660
|
+
log.step("Launching Docker...");
|
|
661
|
+
try {
|
|
662
|
+
await execa("powershell", ["-Command", "Start-Process 'Docker Desktop'"], { stdio: "inherit" });
|
|
663
|
+
log.dim("Docker Desktop launch command sent.");
|
|
664
|
+
} catch {
|
|
665
|
+
log.warn("Could not launch Docker Desktop automatically. It may need to be started manually.");
|
|
666
|
+
}
|
|
667
|
+
log.step("Waiting for Docker...");
|
|
668
|
+
try {
|
|
669
|
+
await waitForDockerDaemon(12e4);
|
|
670
|
+
} catch {
|
|
671
|
+
throw new Error(
|
|
672
|
+
"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"
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
return;
|
|
524
676
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
const type = data.type;
|
|
537
|
-
const content = data.data["content"] || "";
|
|
538
|
-
if (type === "done") {
|
|
539
|
-
tui.displayEvent(type, { content: "Task completed successfully" });
|
|
540
|
-
tui.displayComplete(tui.getElapsedTime());
|
|
541
|
-
process.exit(0);
|
|
542
|
-
} else if (type === "error") {
|
|
543
|
-
tui.displayError(content);
|
|
544
|
-
process.exit(1);
|
|
545
|
-
} else {
|
|
546
|
-
tui.displayEvent(type, { content });
|
|
547
|
-
}
|
|
548
|
-
} catch {
|
|
549
|
-
}
|
|
550
|
-
}
|
|
677
|
+
log.step("Installing Docker Desktop...");
|
|
678
|
+
let installed = false;
|
|
679
|
+
try {
|
|
680
|
+
await execa("choco", ["--version"]);
|
|
681
|
+
log.dim("Chocolatey detected. Installing Docker Desktop via choco...");
|
|
682
|
+
try {
|
|
683
|
+
await execa("choco", ["install", "docker-desktop", "-y"], { stdio: "inherit" });
|
|
684
|
+
installed = true;
|
|
685
|
+
log.success("Docker Desktop installed via Chocolatey.");
|
|
686
|
+
} catch {
|
|
687
|
+
log.warn("Chocolatey install failed. Falling back to direct download...");
|
|
551
688
|
}
|
|
689
|
+
} catch {
|
|
690
|
+
log.dim("Chocolatey not found. Using direct download...");
|
|
552
691
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
692
|
+
if (!installed) {
|
|
693
|
+
log.dim("Downloading Docker Desktop installer from docker.com...");
|
|
694
|
+
try {
|
|
695
|
+
await execa("powershell", [
|
|
696
|
+
"-Command",
|
|
697
|
+
"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"
|
|
698
|
+
], { stdio: "inherit" });
|
|
699
|
+
installed = true;
|
|
700
|
+
log.success("Docker Desktop installed via direct download.");
|
|
701
|
+
} catch {
|
|
702
|
+
throw new Error(
|
|
703
|
+
"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"
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
log.step("Launching Docker...");
|
|
708
|
+
try {
|
|
709
|
+
await execa("powershell", ["-Command", "Start-Process 'Docker Desktop'"], { stdio: "inherit" });
|
|
710
|
+
log.dim("Docker Desktop launch command sent.");
|
|
711
|
+
} catch {
|
|
712
|
+
log.warn("Could not launch Docker Desktop automatically after install.");
|
|
713
|
+
}
|
|
714
|
+
log.step("Waiting for Docker...");
|
|
715
|
+
try {
|
|
716
|
+
await waitForDockerDaemon(12e4);
|
|
717
|
+
} catch {
|
|
718
|
+
throw new Error(
|
|
719
|
+
"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"
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
break;
|
|
558
723
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
console.error("Error:", message);
|
|
562
|
-
process.exit(1);
|
|
724
|
+
default:
|
|
725
|
+
throw new Error(`Unsupported platform: ${p.os}`);
|
|
563
726
|
}
|
|
564
727
|
}
|
|
728
|
+
async function waitForDockerDaemon(timeoutMs) {
|
|
729
|
+
const start = Date.now();
|
|
730
|
+
log.step("Waiting for Docker daemon...");
|
|
731
|
+
while (Date.now() - start < timeoutMs) {
|
|
732
|
+
try {
|
|
733
|
+
await execa("docker", ["info"]);
|
|
734
|
+
log.success("Docker daemon is ready");
|
|
735
|
+
return;
|
|
736
|
+
} catch {
|
|
737
|
+
}
|
|
738
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
739
|
+
}
|
|
740
|
+
throw new Error(
|
|
741
|
+
`Docker daemon did not become ready within ${Math.round(timeoutMs / 1e3)}s.
|
|
565
742
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
import path3 from "path";
|
|
570
|
-
var __dirname = path3.dirname(fileURLToPath(import.meta.url));
|
|
571
|
-
async function dashboardCommand(options) {
|
|
572
|
-
const port = options.port || "4201";
|
|
573
|
-
console.log(`Starting Nexus Dashboard on http://localhost:${port}
|
|
574
|
-
`);
|
|
575
|
-
const dashboardPath = path3.join(__dirname, "../deep-agents/dashboard/server.js");
|
|
576
|
-
const dashboard = spawn("node", [dashboardPath], {
|
|
577
|
-
env: { ...process.env, PORT: port },
|
|
578
|
-
stdio: "inherit"
|
|
579
|
-
});
|
|
580
|
-
dashboard.on("error", (err) => {
|
|
581
|
-
console.error("Failed to start dashboard:", err);
|
|
582
|
-
process.exit(1);
|
|
583
|
-
});
|
|
584
|
-
console.log(`Dashboard ready! Open: http://localhost:${port}`);
|
|
585
|
-
console.log("Press Ctrl+C to stop\n");
|
|
743
|
+
Please ensure Docker is running, then re-run:
|
|
744
|
+
buildwithnexus init`
|
|
745
|
+
);
|
|
586
746
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
"design",
|
|
595
|
-
"plan",
|
|
596
|
-
"architect",
|
|
597
|
-
"structure",
|
|
598
|
-
"outline",
|
|
599
|
-
"roadmap",
|
|
600
|
-
"strategy",
|
|
601
|
-
"organize",
|
|
602
|
-
"breakdown",
|
|
603
|
-
"scope",
|
|
604
|
-
"model",
|
|
605
|
-
"schema"
|
|
606
|
-
];
|
|
607
|
-
var BUILD_KEYWORDS = [
|
|
608
|
-
"build",
|
|
609
|
-
"create",
|
|
610
|
-
"make",
|
|
611
|
-
"write",
|
|
612
|
-
"implement",
|
|
613
|
-
"code",
|
|
614
|
-
"generate",
|
|
615
|
-
"add",
|
|
616
|
-
"fix",
|
|
617
|
-
"update",
|
|
618
|
-
"deploy",
|
|
619
|
-
"run",
|
|
620
|
-
"start",
|
|
621
|
-
"launch",
|
|
622
|
-
"install",
|
|
623
|
-
"set up",
|
|
624
|
-
"setup",
|
|
625
|
-
"refactor",
|
|
626
|
-
"migrate"
|
|
627
|
-
];
|
|
628
|
-
var BRAINSTORM_KEYWORDS = [
|
|
629
|
-
"what",
|
|
630
|
-
"should",
|
|
631
|
-
"idea",
|
|
632
|
-
"ideas",
|
|
633
|
-
"think",
|
|
634
|
-
"consider",
|
|
635
|
-
"suggest",
|
|
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++;
|
|
747
|
+
async function imageExistsLocally(image, tag) {
|
|
748
|
+
const ref = `${image}:${tag}`;
|
|
749
|
+
try {
|
|
750
|
+
await execa("docker", ["image", "inspect", ref]);
|
|
751
|
+
return true;
|
|
752
|
+
} catch {
|
|
753
|
+
return false;
|
|
655
754
|
}
|
|
656
|
-
|
|
657
|
-
|
|
755
|
+
}
|
|
756
|
+
async function pullImage(image, tag) {
|
|
757
|
+
const ref = `${image}:${tag}`;
|
|
758
|
+
log.step(`Pulling image ${ref}...`);
|
|
759
|
+
try {
|
|
760
|
+
await execa("docker", ["pull", ref], { stdio: "inherit" });
|
|
761
|
+
log.success(`Image ${ref} pulled`);
|
|
762
|
+
} catch (err) {
|
|
763
|
+
log.error(`Failed to pull image ${ref}`);
|
|
764
|
+
throw err;
|
|
658
765
|
}
|
|
659
|
-
|
|
660
|
-
|
|
766
|
+
}
|
|
767
|
+
async function startNexus(keys, config) {
|
|
768
|
+
log.step("Starting NEXUS container...");
|
|
769
|
+
try {
|
|
770
|
+
await execa("docker", [
|
|
771
|
+
"run",
|
|
772
|
+
"-d",
|
|
773
|
+
"--name",
|
|
774
|
+
CONTAINER_NAME,
|
|
775
|
+
"-e",
|
|
776
|
+
`ANTHROPIC_API_KEY=${keys.anthropic}`,
|
|
777
|
+
"-e",
|
|
778
|
+
`OPENAI_API_KEY=${keys.openai}`,
|
|
779
|
+
"-p",
|
|
780
|
+
`${config.port}:${config.port}`,
|
|
781
|
+
"buildwithnexus/nexus:latest"
|
|
782
|
+
]);
|
|
783
|
+
log.success(`NEXUS container started on port ${config.port}`);
|
|
784
|
+
} catch (err) {
|
|
785
|
+
log.error("Failed to start NEXUS container");
|
|
786
|
+
throw err;
|
|
661
787
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
788
|
+
}
|
|
789
|
+
async function stopNexus() {
|
|
790
|
+
log.step("Stopping NEXUS container...");
|
|
791
|
+
try {
|
|
792
|
+
await execa("docker", ["rm", "-f", CONTAINER_NAME]);
|
|
793
|
+
log.success("NEXUS container stopped and removed");
|
|
794
|
+
} catch (err) {
|
|
795
|
+
log.error("Failed to stop NEXUS container");
|
|
796
|
+
throw err;
|
|
665
797
|
}
|
|
666
|
-
if (brainstormScore > planScore && brainstormScore > buildScore) return "brainstorm";
|
|
667
|
-
if (buildScore > planScore && buildScore > brainstormScore) return "build";
|
|
668
|
-
if (planScore > 0) return "plan";
|
|
669
|
-
return wordCount > 6 ? "plan" : "build";
|
|
670
798
|
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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);
|
|
799
|
+
async function dockerExec(command) {
|
|
800
|
+
try {
|
|
801
|
+
const { stdout, stderr } = await execa("docker", ["exec", CONTAINER_NAME, "sh", "-c", command]);
|
|
802
|
+
return { stdout, stderr, code: 0 };
|
|
803
|
+
} catch (err) {
|
|
804
|
+
const e = err;
|
|
805
|
+
return { stdout: e.stdout ?? "", stderr: e.stderr ?? "", code: e.exitCode ?? 1 };
|
|
683
806
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
807
|
+
}
|
|
808
|
+
async function startBackend() {
|
|
809
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
810
|
+
const os5 = await import("os");
|
|
811
|
+
const path11 = await import("path");
|
|
812
|
+
const nexusDir = path11.join(os5.homedir(), "Projects", "nexus");
|
|
813
|
+
log.step(`Starting Nexus backend from ${nexusDir}...`);
|
|
814
|
+
const child = spawn2("python", ["-m", "src.deep_agents_server"], {
|
|
815
|
+
cwd: nexusDir,
|
|
816
|
+
detached: true,
|
|
817
|
+
stdio: "ignore",
|
|
818
|
+
env: { ...process.env }
|
|
819
|
+
});
|
|
820
|
+
child.unref();
|
|
821
|
+
log.success("Nexus backend process started");
|
|
822
|
+
}
|
|
823
|
+
async function isNexusRunning() {
|
|
690
824
|
try {
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
825
|
+
const { stdout } = await execa("docker", [
|
|
826
|
+
"ps",
|
|
827
|
+
"--filter",
|
|
828
|
+
`name=^/${CONTAINER_NAME}$`,
|
|
829
|
+
"--format",
|
|
830
|
+
"{{.Names}}"
|
|
831
|
+
]);
|
|
832
|
+
return stdout.trim() === CONTAINER_NAME;
|
|
696
833
|
} catch {
|
|
697
|
-
|
|
698
|
-
process.exit(1);
|
|
834
|
+
return false;
|
|
699
835
|
}
|
|
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);
|
|
723
|
-
console.log("");
|
|
836
|
+
}
|
|
837
|
+
var CONTAINER_NAME, DOCKER_DESKTOP_APP_PATH;
|
|
838
|
+
var init_docker = __esm({
|
|
839
|
+
"src/core/docker.ts"() {
|
|
840
|
+
"use strict";
|
|
841
|
+
init_logger();
|
|
842
|
+
init_platform();
|
|
843
|
+
CONTAINER_NAME = "nexus";
|
|
844
|
+
DOCKER_DESKTOP_APP_PATH = "/Applications/Docker.app";
|
|
724
845
|
}
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
// src/ui/banner.ts
|
|
849
|
+
var banner_exports = {};
|
|
850
|
+
__export(banner_exports, {
|
|
851
|
+
showCompletion: () => showCompletion,
|
|
852
|
+
showPhase: () => showPhase,
|
|
853
|
+
showSecurityPosture: () => showSecurityPosture
|
|
854
|
+
});
|
|
855
|
+
import chalk5 from "chalk";
|
|
856
|
+
function showPhase(phase, total, description) {
|
|
857
|
+
const progress = chalk5.cyan(`[${phase}/${total}]`);
|
|
858
|
+
console.log(`
|
|
859
|
+
${progress} ${chalk5.bold(description)}`);
|
|
725
860
|
}
|
|
726
|
-
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
861
|
+
function showSecurityPosture() {
|
|
862
|
+
const lines = [
|
|
863
|
+
"",
|
|
864
|
+
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"),
|
|
865
|
+
chalk5.bold(" \u2551 ") + chalk5.bold.green("Security Posture") + chalk5.bold(" \u2551"),
|
|
866
|
+
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"),
|
|
867
|
+
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" Triple-nested isolation: Host \u2192 VM \u2192 Docker \u2192 KVM".padEnd(54)) + chalk5.bold("\u2551"),
|
|
868
|
+
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" Network hardened: UFW deny-all, allow 22/80/443/4200".padEnd(54)) + chalk5.bold("\u2551"),
|
|
869
|
+
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" All databases encrypted at rest (AES-256-CBC)".padEnd(54)) + chalk5.bold("\u2551"),
|
|
870
|
+
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" API keys never embedded in VM \u2014 delivered via SCP".padEnd(54)) + chalk5.bold("\u2551"),
|
|
871
|
+
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" SSH-only communication (no exposed network ports)".padEnd(54)) + chalk5.bold("\u2551"),
|
|
872
|
+
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" DLP: secret detection, shell escaping, output redaction".padEnd(54)) + chalk5.bold("\u2551"),
|
|
873
|
+
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" HMAC integrity verification on all key files".padEnd(54)) + chalk5.bold("\u2551"),
|
|
874
|
+
chalk5.bold(" \u2551 ") + chalk5.green("\u2713") + chalk5.white(" Docker: --read-only, no-new-privileges, cap-drop=ALL".padEnd(54)) + chalk5.bold("\u2551"),
|
|
875
|
+
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"),
|
|
876
|
+
chalk5.bold(" \u2551 ") + chalk5.dim("Full details: https://buildwithnexus.dev/security".padEnd(55)) + chalk5.bold("\u2551"),
|
|
877
|
+
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"),
|
|
878
|
+
""
|
|
879
|
+
];
|
|
880
|
+
console.log(lines.join("\n"));
|
|
881
|
+
}
|
|
882
|
+
function showCompletion(urls) {
|
|
883
|
+
const lines = [
|
|
884
|
+
"",
|
|
885
|
+
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"),
|
|
886
|
+
chalk5.green(" \u2551 ") + chalk5.bold.green("NEXUS Runtime is Live!") + chalk5.green(" \u2551"),
|
|
887
|
+
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"),
|
|
888
|
+
chalk5.green(" \u2551 ") + chalk5.white(`Connect: ${urls.ssh}`.padEnd(55)) + chalk5.green("\u2551")
|
|
889
|
+
];
|
|
890
|
+
if (urls.remote) {
|
|
891
|
+
lines.push(chalk5.green(" \u2551 ") + chalk5.white(`Remote: ${urls.remote}`.padEnd(55)) + chalk5.green("\u2551"));
|
|
892
|
+
}
|
|
893
|
+
lines.push(
|
|
894
|
+
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"),
|
|
895
|
+
chalk5.green(" \u2551 ") + chalk5.dim("Quick Start:".padEnd(55)) + chalk5.green("\u2551"),
|
|
896
|
+
chalk5.green(" \u2551 ") + chalk5.white(" buildwithnexus - Interactive shell".padEnd(55)) + chalk5.green("\u2551"),
|
|
897
|
+
chalk5.green(" \u2551 ") + chalk5.white(" buildwithnexus brainstorm - Brainstorm ideas".padEnd(55)) + chalk5.green("\u2551"),
|
|
898
|
+
chalk5.green(" \u2551 ") + chalk5.white(" buildwithnexus status - Check health".padEnd(55)) + chalk5.green("\u2551"),
|
|
899
|
+
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"),
|
|
900
|
+
chalk5.green(" \u2551 ") + chalk5.dim("All commands:".padEnd(55)) + chalk5.green("\u2551"),
|
|
901
|
+
chalk5.green(" \u2551 ") + chalk5.white(" buildwithnexus stop/start/update/logs/ssh/destroy".padEnd(55)) + chalk5.green("\u2551"),
|
|
902
|
+
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"),
|
|
903
|
+
""
|
|
735
904
|
);
|
|
736
|
-
|
|
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;
|
|
905
|
+
console.log(lines.join("\n"));
|
|
742
906
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
907
|
+
var init_banner = __esm({
|
|
908
|
+
"src/ui/banner.ts"() {
|
|
909
|
+
"use strict";
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
// src/bin.ts
|
|
914
|
+
init_init_command();
|
|
915
|
+
import { program } from "commander";
|
|
916
|
+
|
|
917
|
+
// src/cli/tui.ts
|
|
918
|
+
import chalk from "chalk";
|
|
919
|
+
import stringWidth from "string-width";
|
|
920
|
+
var colors = {
|
|
921
|
+
accent: chalk.hex("#7D56F4"),
|
|
922
|
+
// Brand purple
|
|
923
|
+
success: chalk.hex("#00FF87"),
|
|
924
|
+
// Bright green
|
|
925
|
+
warning: chalk.hex("#FFB86C"),
|
|
926
|
+
// Amber
|
|
927
|
+
info: chalk.hex("#8BE9FD"),
|
|
928
|
+
// Cyan
|
|
929
|
+
muted: chalk.gray,
|
|
930
|
+
// Adaptive gray
|
|
931
|
+
error: chalk.red
|
|
932
|
+
};
|
|
933
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
934
|
+
var STATUS_SYMBOLS = {
|
|
935
|
+
done: colors.success("\u2714"),
|
|
936
|
+
active: colors.info("\u25C9"),
|
|
937
|
+
pending: colors.muted("\u25CB"),
|
|
938
|
+
error: colors.error("\u2716")
|
|
939
|
+
};
|
|
940
|
+
var TUI = class {
|
|
941
|
+
taskStartTime = 0;
|
|
942
|
+
eventCount = 0;
|
|
943
|
+
spinnerIndex = 0;
|
|
944
|
+
getSpinner() {
|
|
945
|
+
const frame = SPINNER_FRAMES[this.spinnerIndex % SPINNER_FRAMES.length];
|
|
946
|
+
this.spinnerIndex++;
|
|
947
|
+
return frame;
|
|
948
|
+
}
|
|
949
|
+
displayHeader(task, agent) {
|
|
746
950
|
console.clear();
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
951
|
+
const headerBox = this.makeRoundedBox(
|
|
952
|
+
colors.accent("\u{1F680} NEXUS - Autonomous Agent Orchestration"),
|
|
953
|
+
62,
|
|
954
|
+
colors.accent
|
|
955
|
+
);
|
|
956
|
+
console.log(headerBox);
|
|
957
|
+
console.log("");
|
|
958
|
+
console.log(colors.muted("Task") + colors.muted(": ") + chalk.white(task));
|
|
959
|
+
console.log(colors.muted("Agent") + colors.muted(": ") + colors.info(agent));
|
|
960
|
+
console.log("");
|
|
961
|
+
this.taskStartTime = Date.now();
|
|
962
|
+
}
|
|
963
|
+
displayConnecting() {
|
|
964
|
+
console.log(`${this.getSpinner()} ${colors.warning("Connecting to backend...")}`);
|
|
965
|
+
}
|
|
966
|
+
displayConnected(runId) {
|
|
967
|
+
console.log(`${STATUS_SYMBOLS.done} Connected \u2022 ${colors.muted(`run: ${runId}`)}`);
|
|
968
|
+
console.log("");
|
|
969
|
+
}
|
|
970
|
+
displayStreamStart() {
|
|
971
|
+
console.log(chalk.bold(colors.accent("\u{1F4E1} Streaming Events")));
|
|
972
|
+
console.log("");
|
|
973
|
+
}
|
|
974
|
+
displayPlan(task, steps) {
|
|
975
|
+
console.log("");
|
|
976
|
+
const planHeader = colors.accent("Plan Breakdown");
|
|
977
|
+
const planLines = [];
|
|
978
|
+
steps.forEach((step, i) => {
|
|
979
|
+
planLines.push(` ${STATUS_SYMBOLS.pending} ${chalk.white(`Step ${i + 1}:`)} ${step}`);
|
|
980
|
+
});
|
|
981
|
+
const planBox = this.makeDoubleBox(planHeader, planLines.join("\n"), 62, colors.accent);
|
|
982
|
+
console.log(planBox);
|
|
983
|
+
console.log("");
|
|
984
|
+
}
|
|
985
|
+
displayEvent(type, data) {
|
|
986
|
+
this.eventCount++;
|
|
987
|
+
const content = data["content"] || "";
|
|
988
|
+
if (type === "agent_working") {
|
|
989
|
+
const agent = data["agent"] || "Agent";
|
|
990
|
+
const agentTask = data["task"] || "";
|
|
991
|
+
console.log("");
|
|
992
|
+
console.log(` ${colors.info("\u{1F464}")} ${colors.info(chalk.bold(agent))}`);
|
|
993
|
+
console.log(` ${colors.muted("\u2192")} ${agentTask}`);
|
|
760
994
|
return;
|
|
761
995
|
}
|
|
762
|
-
if (
|
|
763
|
-
const
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
996
|
+
if (type === "agent_result") {
|
|
997
|
+
const result = data["result"] || "";
|
|
998
|
+
let displayResult = result;
|
|
999
|
+
if (displayResult.length > 100) {
|
|
1000
|
+
displayResult = displayResult.substring(0, 97) + "...";
|
|
767
1001
|
}
|
|
1002
|
+
console.log(` ${STATUS_SYMBOLS.done} ${chalk.white(displayResult)}`);
|
|
768
1003
|
return;
|
|
769
1004
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
|
|
1005
|
+
const eventConfig = {
|
|
1006
|
+
thought: { icon: "\u{1F4AD}", color: colors.info },
|
|
1007
|
+
action: { icon: "\u26A1", color: colors.warning },
|
|
1008
|
+
observation: { icon: "\u2713", color: colors.success },
|
|
1009
|
+
started: { icon: "\u25B6", color: colors.info },
|
|
1010
|
+
done: { icon: "\u2728", color: colors.success },
|
|
1011
|
+
execution_complete: { icon: "\u2728", color: colors.success },
|
|
1012
|
+
error: { icon: "\u2716", color: colors.error }
|
|
1013
|
+
};
|
|
1014
|
+
const config = eventConfig[type] || { icon: "\u25CF", color: colors.muted };
|
|
1015
|
+
let displayContent = content;
|
|
1016
|
+
if (displayContent.length > 100) {
|
|
1017
|
+
displayContent = displayContent.substring(0, 97) + "...";
|
|
777
1018
|
}
|
|
1019
|
+
console.log(` ${config.icon} ${config.color(displayContent)}`);
|
|
778
1020
|
}
|
|
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
|
-
}
|
|
1021
|
+
displayResults(summary, todosCompleted) {
|
|
1022
|
+
console.log("");
|
|
1023
|
+
console.log(colors.success("\u2501".repeat(60)));
|
|
1024
|
+
console.log(colors.success.bold("\u2728 Execution Complete"));
|
|
1025
|
+
console.log("");
|
|
1026
|
+
const lines = summary.split("\n");
|
|
1027
|
+
for (const line of lines) {
|
|
1028
|
+
console.log(` ${colors.success("\u2502")} ${chalk.white(line)}`);
|
|
846
1029
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1030
|
+
console.log("");
|
|
1031
|
+
console.log(` ${colors.muted(`${todosCompleted} subtask(s) completed`)}`);
|
|
1032
|
+
console.log("");
|
|
1033
|
+
}
|
|
1034
|
+
displayError(error) {
|
|
1035
|
+
console.log("");
|
|
1036
|
+
console.log(colors.error("\u274C Error"));
|
|
1037
|
+
console.log(colors.error(error));
|
|
1038
|
+
console.log("");
|
|
1039
|
+
}
|
|
1040
|
+
displayComplete(duration) {
|
|
1041
|
+
const seconds = (duration / 1e3).toFixed(1);
|
|
1042
|
+
console.log("");
|
|
1043
|
+
console.log(colors.success.bold(`\u2728 Complete in ${seconds}s`));
|
|
1044
|
+
console.log(colors.muted(`${this.eventCount} event(s) streamed`));
|
|
1045
|
+
console.log("");
|
|
1046
|
+
}
|
|
1047
|
+
displayBox(title, content) {
|
|
1048
|
+
const box = this.makeRoundedBox(title, 60, colors.accent);
|
|
1049
|
+
console.log(box);
|
|
1050
|
+
const lines = content.split("\n");
|
|
1051
|
+
for (const line of lines) {
|
|
1052
|
+
const padded = line.substring(0, 56).padEnd(56);
|
|
1053
|
+
console.log(` ${padded}`);
|
|
851
1054
|
}
|
|
852
|
-
|
|
853
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
854
|
-
console.error(chalk2.red("Error: " + msg));
|
|
855
|
-
return "cancel";
|
|
1055
|
+
console.log("");
|
|
856
1056
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1057
|
+
getElapsedTime() {
|
|
1058
|
+
return Date.now() - this.taskStartTime;
|
|
1059
|
+
}
|
|
1060
|
+
displayModeBar(current) {
|
|
1061
|
+
const modes = ["PLAN", "BUILD", "BRAINSTORM"];
|
|
1062
|
+
const modeConfig = {
|
|
1063
|
+
PLAN: { icon: "\u{1F4CB}", color: colors.info },
|
|
1064
|
+
BUILD: { icon: "\u2699\uFE0F", color: colors.success },
|
|
1065
|
+
BRAINSTORM: { icon: "\u{1F4A1}", color: chalk.blue }
|
|
1066
|
+
};
|
|
1067
|
+
const parts = modes.map((m) => {
|
|
1068
|
+
const config = modeConfig[m];
|
|
1069
|
+
const label = `${config.icon} ${m}`;
|
|
1070
|
+
if (m === current) {
|
|
1071
|
+
return config.color.bold(label);
|
|
1072
|
+
}
|
|
1073
|
+
return colors.muted(label);
|
|
1074
|
+
});
|
|
1075
|
+
console.log(parts.join(colors.muted(" \u2022 ")));
|
|
1076
|
+
console.log(colors.muted("[s] switch mode"));
|
|
1077
|
+
console.log("");
|
|
1078
|
+
}
|
|
1079
|
+
displayModeHeader(mode) {
|
|
1080
|
+
const config = {
|
|
1081
|
+
PLAN: {
|
|
1082
|
+
icon: "\u{1F4CB}",
|
|
1083
|
+
desc: "Break down and review steps",
|
|
1084
|
+
color: colors.info
|
|
1085
|
+
},
|
|
1086
|
+
BUILD: {
|
|
1087
|
+
icon: "\u2699\uFE0F",
|
|
1088
|
+
desc: "Execute with live streaming",
|
|
1089
|
+
color: colors.success
|
|
1090
|
+
},
|
|
1091
|
+
BRAINSTORM: {
|
|
1092
|
+
icon: "\u{1F4A1}",
|
|
1093
|
+
desc: "Free-form Q&A and exploration",
|
|
1094
|
+
color: chalk.blue
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
const c = config[mode];
|
|
1098
|
+
console.log("");
|
|
1099
|
+
console.log(c.color.bold(`${c.icon} ${mode}`));
|
|
1100
|
+
console.log(colors.muted(c.desc));
|
|
1101
|
+
console.log("");
|
|
1102
|
+
}
|
|
1103
|
+
displaySuggestedMode(mode, task) {
|
|
1104
|
+
const modeColor = {
|
|
1105
|
+
PLAN: colors.info,
|
|
1106
|
+
BUILD: colors.success,
|
|
1107
|
+
BRAINSTORM: chalk.blue
|
|
1108
|
+
};
|
|
1109
|
+
const taskPreview = task.length > 45 ? task.substring(0, 42) + "..." : task;
|
|
1110
|
+
console.log("");
|
|
1111
|
+
console.log(
|
|
1112
|
+
colors.muted("Suggested: ") + modeColor[mode].bold(mode) + colors.muted(` for "${taskPreview}"`)
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
displayBrainstormResponse(response) {
|
|
1116
|
+
console.log("");
|
|
1117
|
+
console.log(chalk.blue.bold("\u{1F4A1} Ideas & Analysis"));
|
|
1118
|
+
console.log("");
|
|
1119
|
+
const lines = response.split("\n");
|
|
1120
|
+
for (const line of lines) {
|
|
1121
|
+
if (line.trim()) {
|
|
1122
|
+
console.log(` ${chalk.white(line)}`);
|
|
1123
|
+
} else {
|
|
1124
|
+
console.log("");
|
|
1125
|
+
}
|
|
863
1126
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1127
|
+
console.log("");
|
|
1128
|
+
}
|
|
1129
|
+
displayPermissionPrompt(message) {
|
|
1130
|
+
return colors.accent.bold(message) + colors.muted(" (y/n) ");
|
|
1131
|
+
}
|
|
1132
|
+
padToWidth(text, targetWidth) {
|
|
1133
|
+
const visibleWidth = stringWidth(text);
|
|
1134
|
+
const padding = Math.max(0, targetWidth - visibleWidth);
|
|
1135
|
+
return text + " ".repeat(padding);
|
|
1136
|
+
}
|
|
1137
|
+
truncateToWidth(text, maxWidth) {
|
|
1138
|
+
let result = "";
|
|
1139
|
+
let width = 0;
|
|
1140
|
+
for (const char of text) {
|
|
1141
|
+
const charWidth = stringWidth(char);
|
|
1142
|
+
if (width + charWidth > maxWidth) break;
|
|
1143
|
+
result += char;
|
|
1144
|
+
width += charWidth;
|
|
867
1145
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
1146
|
+
return result;
|
|
1147
|
+
}
|
|
1148
|
+
makeRoundedBox(title, width, borderColor) {
|
|
1149
|
+
const lines = [];
|
|
1150
|
+
const innerWidth = width - 4;
|
|
1151
|
+
const titleText = ` ${title} `;
|
|
1152
|
+
const titleTruncated = this.truncateToWidth(titleText, innerWidth);
|
|
1153
|
+
const titlePadded = this.padToWidth(titleTruncated, innerWidth);
|
|
1154
|
+
lines.push(borderColor("\u256D" + "\u2500".repeat(innerWidth) + "\u256E"));
|
|
1155
|
+
lines.push(borderColor("\u2502") + chalk.bold(titlePadded) + borderColor("\u2502"));
|
|
1156
|
+
lines.push(borderColor("\u2570" + "\u2500".repeat(innerWidth) + "\u256F"));
|
|
1157
|
+
return lines.join("\n");
|
|
1158
|
+
}
|
|
1159
|
+
makeDoubleBox(title, content, width, borderColor) {
|
|
1160
|
+
const lines = [];
|
|
1161
|
+
const innerWidth = width - 4;
|
|
1162
|
+
const titleText = ` ${title} `;
|
|
1163
|
+
const titleTruncated = this.truncateToWidth(titleText, innerWidth);
|
|
1164
|
+
const titlePadded = this.padToWidth(titleTruncated, innerWidth);
|
|
1165
|
+
lines.push(borderColor("\u2554" + "\u2550".repeat(innerWidth) + "\u2557"));
|
|
1166
|
+
lines.push(borderColor("\u2551") + chalk.bold(titlePadded) + borderColor("\u2551"));
|
|
1167
|
+
lines.push(borderColor("\u2560" + "\u2550".repeat(innerWidth) + "\u2563"));
|
|
1168
|
+
const contentLines = content.split("\n");
|
|
1169
|
+
for (const line of contentLines) {
|
|
1170
|
+
const contentWidth = innerWidth - 2;
|
|
1171
|
+
const truncated = this.truncateToWidth(line, contentWidth);
|
|
1172
|
+
const padded = this.padToWidth(truncated, contentWidth);
|
|
1173
|
+
lines.push(borderColor("\u2551") + " " + padded + borderColor("\u2551"));
|
|
872
1174
|
}
|
|
873
|
-
|
|
874
|
-
|
|
1175
|
+
lines.push(borderColor("\u255A" + "\u2550".repeat(innerWidth) + "\u255D"));
|
|
1176
|
+
return lines.join("\n");
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
var tui = new TUI();
|
|
1180
|
+
|
|
1181
|
+
// src/core/config.ts
|
|
1182
|
+
import dotenv from "dotenv";
|
|
1183
|
+
import path3 from "path";
|
|
1184
|
+
import os from "os";
|
|
1185
|
+
function loadApiKeys() {
|
|
1186
|
+
const keyNames = [
|
|
1187
|
+
["ANTHROPIC_API_KEY", "anthropic"],
|
|
1188
|
+
["OPENAI_API_KEY", "openai"],
|
|
1189
|
+
["GOOGLE_API_KEY", "google"]
|
|
1190
|
+
];
|
|
1191
|
+
const result = {};
|
|
1192
|
+
for (const [envName, key] of keyNames) {
|
|
1193
|
+
const raw = process.env[envName];
|
|
1194
|
+
if (raw !== void 0) {
|
|
1195
|
+
if (raw.trim() === "") {
|
|
1196
|
+
console.warn(`WARNING: ${envName} is set but empty - it will be treated as unconfigured`);
|
|
1197
|
+
result[key] = void 0;
|
|
1198
|
+
} else {
|
|
1199
|
+
result[key] = raw;
|
|
1200
|
+
}
|
|
1201
|
+
} else {
|
|
1202
|
+
result[key] = void 0;
|
|
875
1203
|
}
|
|
876
1204
|
}
|
|
1205
|
+
return result;
|
|
877
1206
|
}
|
|
878
|
-
function
|
|
879
|
-
|
|
880
|
-
console.log(chalk2.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"));
|
|
881
|
-
console.log(chalk2.bold.cyan("\u2502") + chalk2.bold.white(" \u{1F4CB} Execution Plan ") + chalk2.bold.cyan("\u2502"));
|
|
882
|
-
console.log(chalk2.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"));
|
|
883
|
-
steps.forEach((step, i) => {
|
|
884
|
-
const label = ` Step ${i + 1}: `;
|
|
885
|
-
const maxContentWidth = 57 - label.length;
|
|
886
|
-
const truncated = step.length > maxContentWidth ? step.substring(0, maxContentWidth - 3) + "..." : step;
|
|
887
|
-
const line = label + truncated;
|
|
888
|
-
const padded = line.padEnd(57);
|
|
889
|
-
console.log(chalk2.bold.cyan("\u2502") + chalk2.white(padded) + chalk2.bold.cyan("\u2502"));
|
|
890
|
-
});
|
|
891
|
-
console.log(chalk2.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"));
|
|
892
|
-
console.log("");
|
|
1207
|
+
function hasAnyKey() {
|
|
1208
|
+
return !!(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.GOOGLE_API_KEY);
|
|
893
1209
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1210
|
+
function validateBackendUrl(url) {
|
|
1211
|
+
let parsed;
|
|
1212
|
+
try {
|
|
1213
|
+
parsed = new URL(url);
|
|
1214
|
+
} catch {
|
|
1215
|
+
return { valid: false, error: `Invalid backend URL: ${url}` };
|
|
1216
|
+
}
|
|
1217
|
+
const hostname = parsed.hostname;
|
|
1218
|
+
const isLocal = hostname === "localhost" || hostname === "127.0.0.1";
|
|
1219
|
+
if (parsed.protocol === "https:") {
|
|
1220
|
+
return { valid: true };
|
|
1221
|
+
}
|
|
1222
|
+
if (isLocal) {
|
|
1223
|
+
return { valid: true };
|
|
902
1224
|
}
|
|
903
|
-
return
|
|
1225
|
+
return {
|
|
1226
|
+
valid: false,
|
|
1227
|
+
error: "WARNING: Backend URL is not localhost and not HTTPS. API keys will be transmitted in plaintext. Use SSH tunnel or HTTPS endpoint."
|
|
1228
|
+
};
|
|
904
1229
|
}
|
|
905
|
-
|
|
906
|
-
|
|
1230
|
+
|
|
1231
|
+
// src/cli/run-command.ts
|
|
1232
|
+
async function runCommand(task, options) {
|
|
1233
|
+
const backendUrl = process.env.BACKEND_URL || "http://localhost:4200";
|
|
1234
|
+
const urlCheck = validateBackendUrl(backendUrl);
|
|
1235
|
+
if (!urlCheck.valid) {
|
|
1236
|
+
console.error(`
|
|
1237
|
+
${urlCheck.error}`);
|
|
1238
|
+
process.exit(1);
|
|
1239
|
+
}
|
|
1240
|
+
tui.displayHeader(task, options.agent);
|
|
907
1241
|
tui.displayConnecting();
|
|
908
|
-
const keys = loadApiKeys();
|
|
909
1242
|
try {
|
|
1243
|
+
let healthOk = false;
|
|
1244
|
+
try {
|
|
1245
|
+
const healthResponse = await fetch(`${backendUrl}/health`);
|
|
1246
|
+
healthOk = healthResponse.ok;
|
|
1247
|
+
} catch {
|
|
1248
|
+
}
|
|
1249
|
+
if (!healthOk) {
|
|
1250
|
+
console.error(
|
|
1251
|
+
"Backend not responding. Start it with:\n buildwithnexus server"
|
|
1252
|
+
);
|
|
1253
|
+
process.exit(1);
|
|
1254
|
+
}
|
|
1255
|
+
const keys = loadApiKeys();
|
|
910
1256
|
const response = await fetch(`${backendUrl}/api/run`, {
|
|
911
1257
|
method: "POST",
|
|
912
1258
|
headers: { "Content-Type": "application/json" },
|
|
913
|
-
body: JSON.stringify({
|
|
1259
|
+
body: JSON.stringify({
|
|
1260
|
+
task,
|
|
1261
|
+
agent_role: options.agent,
|
|
1262
|
+
agent_goal: options.goal || "",
|
|
1263
|
+
api_key: keys.anthropic || "",
|
|
1264
|
+
openai_api_key: keys.openai || "",
|
|
1265
|
+
google_api_key: keys.google || ""
|
|
1266
|
+
})
|
|
914
1267
|
});
|
|
915
1268
|
if (!response.ok) {
|
|
916
|
-
console.error(
|
|
917
|
-
|
|
1269
|
+
console.error("Backend error");
|
|
1270
|
+
console.error(await response.text());
|
|
1271
|
+
process.exit(1);
|
|
918
1272
|
}
|
|
919
1273
|
const { run_id } = await response.json();
|
|
920
1274
|
tui.displayConnected(run_id);
|
|
921
|
-
console.log(chalk2.bold.green("\u2699\uFE0F Executing..."));
|
|
922
1275
|
tui.displayStreamStart();
|
|
923
|
-
const
|
|
924
|
-
const reader = streamResponse.body?.getReader();
|
|
925
|
-
const decoder = new TextDecoder();
|
|
926
|
-
if (!reader) throw new Error("No response body");
|
|
927
|
-
let buffer = "";
|
|
928
|
-
while (true) {
|
|
929
|
-
const { done, value } = await reader.read();
|
|
930
|
-
if (done) break;
|
|
931
|
-
buffer += decoder.decode(value, { stream: true });
|
|
932
|
-
const lines = buffer.split("\n");
|
|
933
|
-
buffer = lines.pop() || "";
|
|
934
|
-
for (const line of lines) {
|
|
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
|
-
}
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
} catch (err) {
|
|
961
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
962
|
-
console.error(chalk2.red("Error: " + msg));
|
|
963
|
-
}
|
|
964
|
-
console.log("");
|
|
965
|
-
console.log(
|
|
966
|
-
chalk2.gray("Options: ") + chalk2.bold("[Enter]") + chalk2.gray(" Done ") + chalk2.bold("[s]") + chalk2.gray(" Switch mode")
|
|
967
|
-
);
|
|
968
|
-
const answer = (await ask(chalk2.bold("> "))).trim().toLowerCase();
|
|
969
|
-
if (answer === "s" || answer === "switch") return "switch";
|
|
970
|
-
return "done";
|
|
971
|
-
}
|
|
972
|
-
async function brainstormModeLoop(task, backendUrl, rl, ask) {
|
|
973
|
-
console.log(chalk2.bold("Starting topic:"), chalk2.white(task));
|
|
974
|
-
console.log(chalk2.gray('Ask follow-up questions. Type "done" to exit, "switch" to change mode.\n'));
|
|
975
|
-
let currentQuestion = task;
|
|
976
|
-
while (true) {
|
|
977
|
-
console.log(chalk2.bold.blue("\u{1F4A1} Thinking..."));
|
|
1276
|
+
const eventSourceUrl = `${backendUrl}/api/stream/${run_id}`;
|
|
978
1277
|
try {
|
|
979
|
-
const
|
|
980
|
-
const
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
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
|
-
}
|
|
1278
|
+
const response2 = await fetch(eventSourceUrl);
|
|
1279
|
+
const reader = response2.body?.getReader();
|
|
1280
|
+
if (!reader) {
|
|
1281
|
+
throw new Error("No response body");
|
|
1282
|
+
}
|
|
1283
|
+
for await (const parsed of parseSSEStream(reader)) {
|
|
1284
|
+
const type = parsed.type;
|
|
1285
|
+
const eventContent = parsed.data["content"] || "";
|
|
1286
|
+
if (type === "done") {
|
|
1287
|
+
tui.displayEvent(type, { content: "Task completed successfully" });
|
|
1288
|
+
tui.displayComplete(tui.getElapsedTime());
|
|
1289
|
+
process.exit(0);
|
|
1290
|
+
} else if (type === "error") {
|
|
1291
|
+
tui.displayError(eventContent);
|
|
1292
|
+
process.exit(1);
|
|
1293
|
+
} else {
|
|
1294
|
+
tui.displayEvent(type, { content: eventContent });
|
|
1044
1295
|
}
|
|
1045
|
-
} else {
|
|
1046
|
-
console.log(chalk2.red("Could not reach backend for brainstorm response."));
|
|
1047
1296
|
}
|
|
1048
|
-
} catch (
|
|
1049
|
-
|
|
1050
|
-
|
|
1297
|
+
} catch (error) {
|
|
1298
|
+
console.error(
|
|
1299
|
+
"\nStream error. Make sure backend is running:\n buildwithnexus server"
|
|
1300
|
+
);
|
|
1301
|
+
process.exit(1);
|
|
1051
1302
|
}
|
|
1052
|
-
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
if (!followUp.trim()) continue;
|
|
1057
|
-
currentQuestion = followUp.trim();
|
|
1303
|
+
} catch (error) {
|
|
1304
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1305
|
+
console.error("Error:", message);
|
|
1306
|
+
process.exit(1);
|
|
1058
1307
|
}
|
|
1059
1308
|
}
|
|
1060
1309
|
|
|
1061
|
-
// src/cli.ts
|
|
1062
|
-
import {
|
|
1063
|
-
import {
|
|
1064
|
-
import
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
spinner.fail(chalk3.red(text));
|
|
1310
|
+
// src/cli/dashboard-command.ts
|
|
1311
|
+
import { spawn } from "child_process";
|
|
1312
|
+
import { fileURLToPath } from "url";
|
|
1313
|
+
import path4 from "path";
|
|
1314
|
+
var __dirname = path4.dirname(fileURLToPath(import.meta.url));
|
|
1315
|
+
async function dashboardCommand(options) {
|
|
1316
|
+
const port = options.port || "4201";
|
|
1317
|
+
console.log(`Starting Nexus Dashboard on http://localhost:${port}
|
|
1318
|
+
`);
|
|
1319
|
+
const dashboardPath = path4.join(__dirname, "../deep-agents/dashboard/server.js");
|
|
1320
|
+
const dashboard = spawn("node", [dashboardPath], {
|
|
1321
|
+
env: { ...process.env, PORT: port },
|
|
1322
|
+
stdio: "inherit"
|
|
1323
|
+
});
|
|
1324
|
+
dashboard.on("error", (err) => {
|
|
1325
|
+
console.error("Failed to start dashboard:", err);
|
|
1326
|
+
process.exit(1);
|
|
1327
|
+
});
|
|
1328
|
+
console.log(`Dashboard ready! Open: http://localhost:${port}`);
|
|
1329
|
+
console.log("Press Ctrl+C to stop\n");
|
|
1082
1330
|
}
|
|
1083
1331
|
|
|
1084
|
-
// src/
|
|
1085
|
-
import
|
|
1086
|
-
|
|
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");
|
|
1111
|
-
}
|
|
1112
|
-
};
|
|
1113
|
-
|
|
1114
|
-
// src/core/platform.ts
|
|
1115
|
-
import os3 from "os";
|
|
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
|
-
};
|
|
1125
|
-
}
|
|
1126
|
-
if (platform === "linux") {
|
|
1127
|
-
return {
|
|
1128
|
-
os: "linux",
|
|
1129
|
-
arch: arch === "arm64" ? "arm64" : "x64",
|
|
1130
|
-
dockerPlatform: arch === "arm64" ? "linux/arm64" : "linux/amd64"
|
|
1131
|
-
};
|
|
1132
|
-
}
|
|
1133
|
-
if (platform === "win32") {
|
|
1134
|
-
return {
|
|
1135
|
-
os: "windows",
|
|
1136
|
-
arch: "x64",
|
|
1137
|
-
dockerPlatform: "linux/amd64"
|
|
1138
|
-
};
|
|
1139
|
-
}
|
|
1140
|
-
throw new Error(`Unsupported platform: ${platform} ${arch}`);
|
|
1141
|
-
}
|
|
1332
|
+
// src/cli/interactive.ts
|
|
1333
|
+
import * as readline2 from "readline";
|
|
1334
|
+
import chalk3 from "chalk";
|
|
1142
1335
|
|
|
1143
|
-
// src/
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1336
|
+
// src/cli/intent-classifier.ts
|
|
1337
|
+
var PLAN_KEYWORDS = [
|
|
1338
|
+
"design",
|
|
1339
|
+
"plan",
|
|
1340
|
+
"architect",
|
|
1341
|
+
"structure",
|
|
1342
|
+
"outline",
|
|
1343
|
+
"roadmap",
|
|
1344
|
+
"strategy",
|
|
1345
|
+
"organize",
|
|
1346
|
+
"breakdown",
|
|
1347
|
+
"scope",
|
|
1348
|
+
"model",
|
|
1349
|
+
"schema"
|
|
1350
|
+
];
|
|
1351
|
+
var BUILD_KEYWORDS = [
|
|
1352
|
+
"build",
|
|
1353
|
+
"create",
|
|
1354
|
+
"make",
|
|
1355
|
+
"write",
|
|
1356
|
+
"implement",
|
|
1357
|
+
"code",
|
|
1358
|
+
"generate",
|
|
1359
|
+
"add",
|
|
1360
|
+
"fix",
|
|
1361
|
+
"update",
|
|
1362
|
+
"deploy",
|
|
1363
|
+
"run",
|
|
1364
|
+
"start",
|
|
1365
|
+
"launch",
|
|
1366
|
+
"install",
|
|
1367
|
+
"set up",
|
|
1368
|
+
"setup",
|
|
1369
|
+
"refactor",
|
|
1370
|
+
"migrate"
|
|
1371
|
+
];
|
|
1372
|
+
var BRAINSTORM_KEYWORDS = [
|
|
1373
|
+
"what",
|
|
1374
|
+
"should",
|
|
1375
|
+
"idea",
|
|
1376
|
+
"ideas",
|
|
1377
|
+
"think",
|
|
1378
|
+
"consider",
|
|
1379
|
+
"suggest",
|
|
1380
|
+
"brainstorm",
|
|
1381
|
+
"explore",
|
|
1382
|
+
"wonder",
|
|
1383
|
+
"might",
|
|
1384
|
+
"could",
|
|
1385
|
+
"would",
|
|
1386
|
+
"how about",
|
|
1387
|
+
"what if",
|
|
1388
|
+
"options",
|
|
1389
|
+
"alternatives",
|
|
1390
|
+
"thoughts"
|
|
1391
|
+
];
|
|
1392
|
+
function classifyIntent(task) {
|
|
1393
|
+
const lower = task.toLowerCase().trim();
|
|
1394
|
+
let planScore = 0;
|
|
1395
|
+
let buildScore = 0;
|
|
1396
|
+
let brainstormScore = 0;
|
|
1397
|
+
for (const kw of PLAN_KEYWORDS) {
|
|
1398
|
+
if (lower.includes(kw)) planScore++;
|
|
1173
1399
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
await execa("/bin/bash", [
|
|
1177
|
-
"-c",
|
|
1178
|
-
"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
1179
|
-
], {
|
|
1180
|
-
stdio: "inherit",
|
|
1181
|
-
env: { ...process.env, NONINTERACTIVE: "1" }
|
|
1182
|
-
});
|
|
1183
|
-
} catch {
|
|
1184
|
-
throw new Error(
|
|
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
|
-
);
|
|
1400
|
+
for (const kw of BUILD_KEYWORDS) {
|
|
1401
|
+
if (lower.includes(kw)) buildScore++;
|
|
1187
1402
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
} catch {
|
|
1191
|
-
const armPath = "/opt/homebrew/bin";
|
|
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.");
|
|
1403
|
+
for (const kw of BRAINSTORM_KEYWORDS) {
|
|
1404
|
+
if (lower.includes(kw)) brainstormScore++;
|
|
1195
1405
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
} catch {
|
|
1200
|
-
throw new Error(
|
|
1201
|
-
"Homebrew was installed but is not available on PATH.\n\n Try opening a new terminal and re-running:\n buildwithnexus init"
|
|
1202
|
-
);
|
|
1406
|
+
const wordCount = lower.split(/\s+/).length;
|
|
1407
|
+
if (wordCount > 12 && planScore === buildScore && buildScore === brainstormScore) {
|
|
1408
|
+
return "plan";
|
|
1203
1409
|
}
|
|
1410
|
+
if (brainstormScore > planScore && brainstormScore > buildScore) return "brainstorm";
|
|
1411
|
+
if (buildScore > planScore && buildScore > brainstormScore) return "build";
|
|
1412
|
+
if (planScore > 0) return "plan";
|
|
1413
|
+
return wordCount > 6 ? "plan" : "build";
|
|
1204
1414
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
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}`);
|
|
1272
|
-
try {
|
|
1273
|
-
await execa("open", [DOCKER_DESKTOP_APP_PATH]);
|
|
1274
|
-
postInstallLaunched = true;
|
|
1275
|
-
log.dim(`Launch command succeeded via ${DOCKER_DESKTOP_APP_PATH}`);
|
|
1276
|
-
} catch {
|
|
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
|
-
}
|
|
1288
|
-
}
|
|
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
|
-
}
|
|
1298
|
-
case "linux": {
|
|
1299
|
-
const linuxBinaryExists = await isDockerInstalledButNotRunning();
|
|
1300
|
-
if (linuxBinaryExists) {
|
|
1301
|
-
log.step("Docker is installed but the daemon is not running.");
|
|
1302
|
-
log.step("Starting Docker daemon...");
|
|
1303
|
-
try {
|
|
1304
|
-
await execa("sudo", ["systemctl", "start", "docker"], { stdio: "inherit" });
|
|
1305
|
-
log.dim("Started Docker daemon via systemctl.");
|
|
1306
|
-
} catch {
|
|
1307
|
-
try {
|
|
1308
|
-
await execa("sudo", ["service", "docker", "start"], { stdio: "inherit" });
|
|
1309
|
-
log.dim("Started Docker daemon via service command.");
|
|
1310
|
-
} catch {
|
|
1311
|
-
throw new Error(
|
|
1312
|
-
"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"
|
|
1313
|
-
);
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
log.step("Waiting for Docker...");
|
|
1317
|
-
await waitForDockerDaemon(3e4);
|
|
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...");
|
|
1415
|
+
|
|
1416
|
+
// src/cli/interactive.ts
|
|
1417
|
+
init_secrets();
|
|
1418
|
+
|
|
1419
|
+
// src/core/sse-parser.ts
|
|
1420
|
+
async function* parseSSEStream2(reader) {
|
|
1421
|
+
const decoder = new TextDecoder();
|
|
1422
|
+
let buffer = "";
|
|
1423
|
+
while (true) {
|
|
1424
|
+
const { done, value } = await reader.read();
|
|
1425
|
+
if (done) break;
|
|
1426
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1427
|
+
const lines = buffer.split("\n");
|
|
1428
|
+
buffer = lines.pop() || "";
|
|
1429
|
+
for (const line of lines) {
|
|
1430
|
+
if (!line.startsWith("data: ")) continue;
|
|
1341
1431
|
try {
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
} catch {
|
|
1345
|
-
|
|
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
|
-
}
|
|
1432
|
+
const parsed = JSON.parse(line.slice(6));
|
|
1433
|
+
yield parsed;
|
|
1434
|
+
} catch (e) {
|
|
1435
|
+
if (process.env.LOG_LEVEL === "debug") console.error("SSE parse error:", e);
|
|
1353
1436
|
}
|
|
1354
|
-
log.step("Waiting for Docker...");
|
|
1355
|
-
await waitForDockerDaemon(3e4);
|
|
1356
|
-
break;
|
|
1357
1437
|
}
|
|
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
|
-
}
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
// src/cli/interactive.ts
|
|
1442
|
+
init_docker();
|
|
1443
|
+
var appVersion = true ? "0.7.2" : pkg.version;
|
|
1444
|
+
async function interactiveMode() {
|
|
1445
|
+
const backendUrl = process.env.BACKEND_URL || "http://localhost:4200";
|
|
1446
|
+
const urlCheck = validateBackendUrl(backendUrl);
|
|
1447
|
+
if (!urlCheck.valid) {
|
|
1448
|
+
console.error(chalk3.red(`
|
|
1449
|
+
${urlCheck.error}`));
|
|
1450
|
+
process.exit(1);
|
|
1451
|
+
}
|
|
1452
|
+
if (!hasAnyKey()) {
|
|
1453
|
+
try {
|
|
1454
|
+
const storedKeys = loadKeys();
|
|
1455
|
+
if (storedKeys) {
|
|
1456
|
+
if (storedKeys.ANTHROPIC_API_KEY) process.env.ANTHROPIC_API_KEY = storedKeys.ANTHROPIC_API_KEY;
|
|
1457
|
+
if (storedKeys.OPENAI_API_KEY) process.env.OPENAI_API_KEY = storedKeys.OPENAI_API_KEY;
|
|
1458
|
+
if (storedKeys.GOOGLE_API_KEY) process.env.GOOGLE_API_KEY = storedKeys.GOOGLE_API_KEY;
|
|
1408
1459
|
}
|
|
1409
|
-
|
|
1460
|
+
} catch {
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
if (!hasAnyKey()) {
|
|
1464
|
+
console.log(chalk3.cyan("\n\u{1F527} Configure API Keys\n"));
|
|
1465
|
+
const { deepAgentsInitCommand: deepAgentsInitCommand2 } = await Promise.resolve().then(() => (init_init_command(), init_command_exports));
|
|
1466
|
+
await deepAgentsInitCommand2();
|
|
1467
|
+
if (!hasAnyKey()) {
|
|
1468
|
+
console.error("Error: At least one API key is required to use buildwithnexus.");
|
|
1469
|
+
process.exit(1);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
const keys = loadApiKeys();
|
|
1473
|
+
console.log(chalk3.green("\n\u2713 Keys configured!"));
|
|
1474
|
+
console.log(chalk3.gray(` Anthropic: ${keys.anthropic ? "\u2713" : "\u2717"}`));
|
|
1475
|
+
console.log(chalk3.gray(` OpenAI: ${keys.openai ? "\u2713" : "\u2717"}`));
|
|
1476
|
+
console.log(chalk3.gray(` Google: ${keys.google ? "\u2713" : "\u2717"}`));
|
|
1477
|
+
console.log(chalk3.gray(` (Run 'da-init' to reconfigure)
|
|
1478
|
+
`));
|
|
1479
|
+
async function waitForBackend() {
|
|
1480
|
+
for (let i = 0; i < 5; i++) {
|
|
1481
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
1410
1482
|
try {
|
|
1411
|
-
|
|
1412
|
-
|
|
1483
|
+
const retryResponse = await fetch(`${backendUrl}/health`, { signal: AbortSignal.timeout(1e4) });
|
|
1484
|
+
if (retryResponse.ok) return true;
|
|
1413
1485
|
} catch {
|
|
1414
|
-
log.warn("Could not launch Docker Desktop automatically after install.");
|
|
1415
1486
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1487
|
+
}
|
|
1488
|
+
return false;
|
|
1489
|
+
}
|
|
1490
|
+
try {
|
|
1491
|
+
const response = await fetch(`${backendUrl}/health`, { signal: AbortSignal.timeout(1e4) });
|
|
1492
|
+
if (!response.ok) {
|
|
1493
|
+
console.log(chalk3.yellow("\u26A0\uFE0F Backend not responding, starting..."));
|
|
1494
|
+
await startBackend();
|
|
1495
|
+
const ready = await waitForBackend();
|
|
1496
|
+
if (!ready) {
|
|
1497
|
+
console.error(chalk3.red("\u274C Backend failed to start. Run: buildwithnexus server"));
|
|
1498
|
+
process.exit(1);
|
|
1423
1499
|
}
|
|
1424
|
-
break;
|
|
1425
1500
|
}
|
|
1426
|
-
|
|
1427
|
-
|
|
1501
|
+
} catch {
|
|
1502
|
+
console.log(chalk3.yellow("\u26A0\uFE0F Backend not accessible, attempting to start..."));
|
|
1503
|
+
await startBackend();
|
|
1504
|
+
const ready = await waitForBackend();
|
|
1505
|
+
if (!ready) {
|
|
1506
|
+
console.error(chalk3.red("\u274C Backend failed to start. Run: buildwithnexus server"));
|
|
1507
|
+
process.exit(1);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
const rl = readline2.createInterface({
|
|
1511
|
+
input: process.stdin,
|
|
1512
|
+
output: process.stdout
|
|
1513
|
+
});
|
|
1514
|
+
const ask = (question) => new Promise((resolve) => rl.question(question, resolve));
|
|
1515
|
+
console.clear();
|
|
1516
|
+
console.log(chalk3.gray("Welcome! Describe what you want the AI agents to do."));
|
|
1517
|
+
console.log(chalk3.gray('Type "exit" to quit.\n'));
|
|
1518
|
+
while (true) {
|
|
1519
|
+
const task = await ask(chalk3.bold.blue("\u{1F4DD} Task: "));
|
|
1520
|
+
if (task.toLowerCase() === "exit") {
|
|
1521
|
+
console.log(chalk3.yellow("\nGoodbye! \u{1F44B}\n"));
|
|
1522
|
+
rl.close();
|
|
1523
|
+
process.exit(0);
|
|
1524
|
+
}
|
|
1525
|
+
if (!task.trim()) {
|
|
1526
|
+
console.log(chalk3.red("Please enter a task.\n"));
|
|
1527
|
+
continue;
|
|
1528
|
+
}
|
|
1529
|
+
const suggestedMode = classifyIntent(task).toUpperCase();
|
|
1530
|
+
tui.displaySuggestedMode(suggestedMode, task);
|
|
1531
|
+
const currentMode = await selectMode(suggestedMode, ask);
|
|
1532
|
+
await runModeLoop(currentMode, task, backendUrl, ask);
|
|
1533
|
+
console.log("");
|
|
1428
1534
|
}
|
|
1429
1535
|
}
|
|
1430
|
-
async function
|
|
1431
|
-
const
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1536
|
+
async function selectMode(suggested, ask) {
|
|
1537
|
+
const modeColor = {
|
|
1538
|
+
PLAN: chalk3.cyan,
|
|
1539
|
+
BUILD: chalk3.green,
|
|
1540
|
+
BRAINSTORM: chalk3.blue
|
|
1541
|
+
};
|
|
1542
|
+
console.log("");
|
|
1543
|
+
console.log(
|
|
1544
|
+
chalk3.gray("Press ") + chalk3.bold("Enter") + chalk3.gray(" to use ") + modeColor[suggested](suggested) + chalk3.gray(" or choose a mode:")
|
|
1545
|
+
);
|
|
1546
|
+
console.log(
|
|
1547
|
+
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")
|
|
1548
|
+
);
|
|
1549
|
+
const answer = await ask(chalk3.gray("> "));
|
|
1550
|
+
const lower = answer.trim().toLowerCase();
|
|
1551
|
+
if (lower === "1" || lower === "p" || lower === "plan") return "PLAN";
|
|
1552
|
+
if (lower === "2" || lower === "b" || lower === "build") return "BUILD";
|
|
1553
|
+
if (lower === "3" || lower === "bs" || lower === "br" || lower === "brainstorm") return "BRAINSTORM";
|
|
1554
|
+
return suggested;
|
|
1555
|
+
}
|
|
1556
|
+
async function runModeLoop(mode, task, backendUrl, ask) {
|
|
1557
|
+
let currentMode = mode;
|
|
1558
|
+
while (true) {
|
|
1559
|
+
console.clear();
|
|
1560
|
+
printAppHeader();
|
|
1561
|
+
tui.displayModeBar(currentMode);
|
|
1562
|
+
tui.displayModeHeader(currentMode);
|
|
1563
|
+
if (currentMode === "PLAN") {
|
|
1564
|
+
const next = await planModeLoop(task, backendUrl, ask);
|
|
1565
|
+
if (next === "BUILD") {
|
|
1566
|
+
currentMode = "BUILD";
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
if (next === "switch") {
|
|
1570
|
+
currentMode = await promptModeSwitch(currentMode, ask);
|
|
1571
|
+
continue;
|
|
1572
|
+
}
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
if (currentMode === "BUILD") {
|
|
1576
|
+
const next = await buildModeLoop(task, backendUrl, ask);
|
|
1577
|
+
if (next === "switch") {
|
|
1578
|
+
currentMode = await promptModeSwitch(currentMode, ask);
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
if (currentMode === "BRAINSTORM") {
|
|
1584
|
+
const next = await brainstormModeLoop(task, backendUrl, ask);
|
|
1585
|
+
if (next === "switch") {
|
|
1586
|
+
currentMode = await promptModeSwitch(currentMode, ask);
|
|
1587
|
+
continue;
|
|
1588
|
+
}
|
|
1437
1589
|
return;
|
|
1438
|
-
} catch {
|
|
1439
1590
|
}
|
|
1440
|
-
await new Promise((r) => setTimeout(r, 3e3));
|
|
1441
1591
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1592
|
+
}
|
|
1593
|
+
function printAppHeader() {
|
|
1594
|
+
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"));
|
|
1595
|
+
console.log(
|
|
1596
|
+
chalk3.cyan("\u2551") + chalk3.bold.white(" Nexus - Autonomous Agent Orchestration ") + chalk3.cyan("\u2551")
|
|
1597
|
+
);
|
|
1598
|
+
const versionLine = ` v${appVersion}`;
|
|
1599
|
+
console.log(
|
|
1600
|
+
chalk3.cyan("\u2551") + chalk3.dim(versionLine.padEnd(60)) + chalk3.cyan("\u2551")
|
|
1447
1601
|
);
|
|
1602
|
+
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"));
|
|
1603
|
+
console.log("");
|
|
1448
1604
|
}
|
|
1449
|
-
async function
|
|
1450
|
-
const
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1605
|
+
async function promptModeSwitch(current, ask) {
|
|
1606
|
+
const others = ["PLAN", "BUILD", "BRAINSTORM"].filter((m) => m !== current);
|
|
1607
|
+
console.log("");
|
|
1608
|
+
console.log(
|
|
1609
|
+
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("]")
|
|
1610
|
+
);
|
|
1611
|
+
const answer = await ask(chalk3.gray("> "));
|
|
1612
|
+
const n = parseInt(answer.trim(), 10);
|
|
1613
|
+
if (n === 1) return others[0];
|
|
1614
|
+
if (n === 2) return others[1];
|
|
1615
|
+
return current;
|
|
1457
1616
|
}
|
|
1458
|
-
async function
|
|
1459
|
-
|
|
1460
|
-
log
|
|
1617
|
+
async function planModeLoop(task, backendUrl, ask) {
|
|
1618
|
+
console.log(chalk3.bold("Task:"), chalk3.white(task));
|
|
1619
|
+
console.log("");
|
|
1620
|
+
console.log(chalk3.yellow("\u23F3 Fetching plan from backend..."));
|
|
1621
|
+
let steps = [];
|
|
1622
|
+
const keys = loadApiKeys();
|
|
1461
1623
|
try {
|
|
1462
|
-
|
|
1463
|
-
|
|
1624
|
+
const response = await fetch(`${backendUrl}/api/run`, {
|
|
1625
|
+
method: "POST",
|
|
1626
|
+
headers: { "Content-Type": "application/json" },
|
|
1627
|
+
body: JSON.stringify({ task, agent_role: "engineer", agent_goal: "", api_key: keys.anthropic || "", openai_api_key: keys.openai || "", google_api_key: keys.google || "" }),
|
|
1628
|
+
signal: AbortSignal.timeout(12e4)
|
|
1629
|
+
});
|
|
1630
|
+
if (!response.ok) {
|
|
1631
|
+
console.error(chalk3.red("Backend error \u2014 cannot fetch plan."));
|
|
1632
|
+
return "cancel";
|
|
1633
|
+
}
|
|
1634
|
+
const { run_id } = await response.json();
|
|
1635
|
+
tui.displayConnected(run_id);
|
|
1636
|
+
const streamResponse = await fetch(`${backendUrl}/api/stream/${run_id}`, { signal: AbortSignal.timeout(12e4) });
|
|
1637
|
+
const reader = streamResponse.body?.getReader();
|
|
1638
|
+
if (!reader) throw new Error("No response body");
|
|
1639
|
+
let planReceived = false;
|
|
1640
|
+
for await (const parsed of parseSSEStream2(reader)) {
|
|
1641
|
+
if (parsed.type === "plan") {
|
|
1642
|
+
steps = parsed.data["steps"] || [];
|
|
1643
|
+
planReceived = true;
|
|
1644
|
+
reader.cancel();
|
|
1645
|
+
break;
|
|
1646
|
+
} else if (parsed.type === "error") {
|
|
1647
|
+
const errorMsg = parsed.data["error"] || parsed.data["content"] || "Unknown error";
|
|
1648
|
+
tui.displayError(errorMsg);
|
|
1649
|
+
reader.cancel();
|
|
1650
|
+
return "cancel";
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
if (!planReceived || steps.length === 0) {
|
|
1654
|
+
console.log(chalk3.yellow("No plan received from backend."));
|
|
1655
|
+
steps = ["(no steps returned \u2014 execute anyway?)"];
|
|
1656
|
+
}
|
|
1464
1657
|
} catch (err) {
|
|
1465
|
-
|
|
1466
|
-
|
|
1658
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1659
|
+
console.error(chalk3.red("Error: " + msg));
|
|
1660
|
+
return "cancel";
|
|
1661
|
+
}
|
|
1662
|
+
displayPlanSteps(steps);
|
|
1663
|
+
while (true) {
|
|
1664
|
+
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"));
|
|
1665
|
+
const answer = (await ask(tui.displayPermissionPrompt("Execute this plan?"))).trim().toLowerCase();
|
|
1666
|
+
if (answer === "" || answer === "y") {
|
|
1667
|
+
return "BUILD";
|
|
1668
|
+
}
|
|
1669
|
+
if (answer === "n" || answer === "\x1B") {
|
|
1670
|
+
console.log(chalk3.yellow("\nExecution cancelled.\n"));
|
|
1671
|
+
return "cancel";
|
|
1672
|
+
}
|
|
1673
|
+
if (answer === "e" || answer === "edit") {
|
|
1674
|
+
steps = await editPlanSteps(steps, ask);
|
|
1675
|
+
displayPlanSteps(steps);
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1678
|
+
if (answer === "s" || answer === "switch") {
|
|
1679
|
+
return "switch";
|
|
1680
|
+
}
|
|
1467
1681
|
}
|
|
1468
1682
|
}
|
|
1469
|
-
|
|
1470
|
-
log
|
|
1683
|
+
function displayPlanSteps(steps) {
|
|
1684
|
+
console.log("");
|
|
1685
|
+
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"));
|
|
1686
|
+
console.log(chalk3.bold.cyan("\u2502") + chalk3.bold.white(" \u{1F4CB} Execution Plan ") + chalk3.bold.cyan("\u2502"));
|
|
1687
|
+
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"));
|
|
1688
|
+
steps.forEach((step, i) => {
|
|
1689
|
+
const label = ` Step ${i + 1}: `;
|
|
1690
|
+
const maxContentWidth = 57 - label.length;
|
|
1691
|
+
const truncated = step.length > maxContentWidth ? step.substring(0, maxContentWidth - 3) + "..." : step;
|
|
1692
|
+
const line = label + truncated;
|
|
1693
|
+
const padded = line.padEnd(57);
|
|
1694
|
+
console.log(chalk3.bold.cyan("\u2502") + chalk3.white(padded) + chalk3.bold.cyan("\u2502"));
|
|
1695
|
+
});
|
|
1696
|
+
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"));
|
|
1697
|
+
console.log("");
|
|
1698
|
+
}
|
|
1699
|
+
async function editPlanSteps(steps, ask) {
|
|
1700
|
+
console.log(chalk3.gray("Enter step number to edit, or press Enter to finish editing:"));
|
|
1701
|
+
const numStr = await ask(chalk3.bold("Step #: "));
|
|
1702
|
+
const n = parseInt(numStr.trim(), 10);
|
|
1703
|
+
if (!isNaN(n) && n >= 1 && n <= steps.length) {
|
|
1704
|
+
console.log(chalk3.gray(`Current: ${steps[n - 1]}`));
|
|
1705
|
+
const updated = await ask(chalk3.bold("New text: "));
|
|
1706
|
+
if (updated.trim()) steps[n - 1] = updated.trim();
|
|
1707
|
+
}
|
|
1708
|
+
return steps;
|
|
1709
|
+
}
|
|
1710
|
+
async function buildModeLoop(task, backendUrl, ask) {
|
|
1711
|
+
console.log(chalk3.bold("Task:"), chalk3.white(task));
|
|
1712
|
+
tui.displayConnecting();
|
|
1713
|
+
const keys = loadApiKeys();
|
|
1471
1714
|
try {
|
|
1472
|
-
await
|
|
1473
|
-
"
|
|
1474
|
-
"-
|
|
1475
|
-
"
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
"
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1715
|
+
const response = await fetch(`${backendUrl}/api/run`, {
|
|
1716
|
+
method: "POST",
|
|
1717
|
+
headers: { "Content-Type": "application/json" },
|
|
1718
|
+
body: JSON.stringify({ task, agent_role: "engineer", agent_goal: "", api_key: keys.anthropic || "", openai_api_key: keys.openai || "", google_api_key: keys.google || "" }),
|
|
1719
|
+
signal: AbortSignal.timeout(12e4)
|
|
1720
|
+
});
|
|
1721
|
+
if (!response.ok) {
|
|
1722
|
+
console.error(chalk3.red("Backend error"));
|
|
1723
|
+
return "done";
|
|
1724
|
+
}
|
|
1725
|
+
const { run_id } = await response.json();
|
|
1726
|
+
tui.displayConnected(run_id);
|
|
1727
|
+
console.log(chalk3.bold.green("\u2699\uFE0F Executing..."));
|
|
1728
|
+
tui.displayStreamStart();
|
|
1729
|
+
const streamResponse = await fetch(`${backendUrl}/api/stream/${run_id}`, { signal: AbortSignal.timeout(12e4) });
|
|
1730
|
+
const reader = streamResponse.body?.getReader();
|
|
1731
|
+
if (!reader) throw new Error("No response body");
|
|
1732
|
+
for await (const parsed of parseSSEStream2(reader)) {
|
|
1733
|
+
const type = parsed.type;
|
|
1734
|
+
if (type === "execution_complete") {
|
|
1735
|
+
const summary = parsed.data["summary"] || "";
|
|
1736
|
+
const count = parsed.data["todos_completed"] || 0;
|
|
1737
|
+
tui.displayResults(summary, count);
|
|
1738
|
+
tui.displayComplete(tui.getElapsedTime());
|
|
1739
|
+
break;
|
|
1740
|
+
} else if (type === "done") {
|
|
1741
|
+
tui.displayEvent(type, { content: "Task completed successfully" });
|
|
1742
|
+
tui.displayComplete(tui.getElapsedTime());
|
|
1743
|
+
break;
|
|
1744
|
+
} else if (type === "error") {
|
|
1745
|
+
const errorMsg = parsed.data["error"] || parsed.data["content"] || "Unknown error";
|
|
1746
|
+
tui.displayError(errorMsg);
|
|
1747
|
+
break;
|
|
1748
|
+
} else if (type !== "plan") {
|
|
1749
|
+
tui.displayEvent(type, parsed.data);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1486
1752
|
} catch (err) {
|
|
1487
|
-
|
|
1488
|
-
|
|
1753
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1754
|
+
console.error(chalk3.red("Error: " + msg));
|
|
1755
|
+
}
|
|
1756
|
+
console.log("");
|
|
1757
|
+
console.log(
|
|
1758
|
+
chalk3.gray("Options: ") + chalk3.bold("[Enter]") + chalk3.gray(" Done ") + chalk3.bold("[s]") + chalk3.gray(" Switch mode")
|
|
1759
|
+
);
|
|
1760
|
+
const answer = (await ask(chalk3.bold("> "))).trim().toLowerCase();
|
|
1761
|
+
if (answer === "s" || answer === "switch") return "switch";
|
|
1762
|
+
return "done";
|
|
1763
|
+
}
|
|
1764
|
+
async function brainstormModeLoop(task, backendUrl, ask) {
|
|
1765
|
+
console.log(chalk3.bold("Starting topic:"), chalk3.white(task));
|
|
1766
|
+
console.log(chalk3.gray('Ask follow-up questions. Type "done" to exit, "switch" to change mode.\n'));
|
|
1767
|
+
let currentQuestion = task;
|
|
1768
|
+
while (true) {
|
|
1769
|
+
console.log(chalk3.bold.blue("\u{1F4A1} Thinking..."));
|
|
1770
|
+
try {
|
|
1771
|
+
const keys = loadApiKeys();
|
|
1772
|
+
const response = await fetch(`${backendUrl}/api/run`, {
|
|
1773
|
+
method: "POST",
|
|
1774
|
+
headers: { "Content-Type": "application/json" },
|
|
1775
|
+
body: JSON.stringify({
|
|
1776
|
+
task: currentQuestion,
|
|
1777
|
+
agent_role: "brainstorm",
|
|
1778
|
+
agent_goal: "Generate ideas, considerations, and suggestions. Be concise and helpful.",
|
|
1779
|
+
api_key: keys.anthropic || "",
|
|
1780
|
+
openai_api_key: keys.openai || "",
|
|
1781
|
+
google_api_key: keys.google || ""
|
|
1782
|
+
}),
|
|
1783
|
+
signal: AbortSignal.timeout(12e4)
|
|
1784
|
+
});
|
|
1785
|
+
if (response.ok) {
|
|
1786
|
+
const { run_id } = await response.json();
|
|
1787
|
+
const streamResponse = await fetch(`${backendUrl}/api/stream/${run_id}`, { signal: AbortSignal.timeout(12e4) });
|
|
1788
|
+
const reader = streamResponse.body?.getReader();
|
|
1789
|
+
if (reader) {
|
|
1790
|
+
let responseText = "";
|
|
1791
|
+
for await (const parsed of parseSSEStream2(reader)) {
|
|
1792
|
+
const type = parsed.type;
|
|
1793
|
+
const data = parsed.data;
|
|
1794
|
+
if (type === "done" || type === "execution_complete") {
|
|
1795
|
+
const summary = data["summary"] || "";
|
|
1796
|
+
if (summary) responseText = summary;
|
|
1797
|
+
break;
|
|
1798
|
+
} else if (type === "error") {
|
|
1799
|
+
const errorMsg = data["error"] || data["content"] || "Unknown error";
|
|
1800
|
+
responseText += errorMsg + "\n";
|
|
1801
|
+
break;
|
|
1802
|
+
} else if (type === "thought" || type === "observation") {
|
|
1803
|
+
const content = data["content"] || "";
|
|
1804
|
+
if (content) responseText += content + "\n";
|
|
1805
|
+
} else if (type === "agent_response" || type === "agent_result") {
|
|
1806
|
+
const content = data["content"] || data["result"] || "";
|
|
1807
|
+
if (content) responseText += content + "\n";
|
|
1808
|
+
} else if (type === "action") {
|
|
1809
|
+
const content = data["content"] || "";
|
|
1810
|
+
if (content) responseText += content + "\n";
|
|
1811
|
+
} else if (type === "agent_working") {
|
|
1812
|
+
} else if (type !== "plan") {
|
|
1813
|
+
const content = data["content"] || data["response"] || "";
|
|
1814
|
+
if (content) responseText += content + "\n";
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
if (responseText.trim()) {
|
|
1818
|
+
tui.displayBrainstormResponse(responseText.trim());
|
|
1819
|
+
} else {
|
|
1820
|
+
console.log(chalk3.gray("(No response received from agent)"));
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
} else {
|
|
1824
|
+
console.log(chalk3.red("Could not reach backend for brainstorm response."));
|
|
1825
|
+
}
|
|
1826
|
+
} catch (err) {
|
|
1827
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1828
|
+
console.error(chalk3.red("Error: " + msg));
|
|
1829
|
+
}
|
|
1830
|
+
const followUp = await ask(chalk3.bold.blue("\u{1F4AC} You: "));
|
|
1831
|
+
const lower = followUp.trim().toLowerCase();
|
|
1832
|
+
if (lower === "done" || lower === "exit") return "done";
|
|
1833
|
+
if (lower === "switch") return "switch";
|
|
1834
|
+
if (!followUp.trim()) continue;
|
|
1835
|
+
currentQuestion = followUp.trim();
|
|
1489
1836
|
}
|
|
1490
1837
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1838
|
+
|
|
1839
|
+
// src/commands/install.ts
|
|
1840
|
+
import { Command } from "commander";
|
|
1841
|
+
import { execa as execa2 } from "execa";
|
|
1842
|
+
|
|
1843
|
+
// src/ui/spinner.ts
|
|
1844
|
+
import ora from "ora";
|
|
1845
|
+
import chalk4 from "chalk";
|
|
1846
|
+
function createSpinner(text) {
|
|
1847
|
+
return ora({ text, color: "cyan", spinner: "dots" });
|
|
1500
1848
|
}
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
const { stdout, stderr } = await execa("docker", ["exec", CONTAINER_NAME, "sh", "-c", command]);
|
|
1504
|
-
return { stdout, stderr, code: 0 };
|
|
1505
|
-
} catch (err) {
|
|
1506
|
-
const e = err;
|
|
1507
|
-
return { stdout: e.stdout ?? "", stderr: e.stderr ?? "", code: e.exitCode ?? 1 };
|
|
1508
|
-
}
|
|
1849
|
+
function succeed(spinner, text) {
|
|
1850
|
+
spinner.succeed(chalk4.green(text));
|
|
1509
1851
|
}
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
const { stdout } = await execa("docker", [
|
|
1513
|
-
"ps",
|
|
1514
|
-
"--filter",
|
|
1515
|
-
`name=^/${CONTAINER_NAME}$`,
|
|
1516
|
-
"--format",
|
|
1517
|
-
"{{.Names}}"
|
|
1518
|
-
]);
|
|
1519
|
-
return stdout.trim() === CONTAINER_NAME;
|
|
1520
|
-
} catch {
|
|
1521
|
-
return false;
|
|
1522
|
-
}
|
|
1852
|
+
function fail(spinner, text) {
|
|
1853
|
+
spinner.fail(chalk4.red(text));
|
|
1523
1854
|
}
|
|
1524
1855
|
|
|
1525
1856
|
// src/commands/install.ts
|
|
1857
|
+
init_logger();
|
|
1858
|
+
init_platform();
|
|
1859
|
+
init_docker();
|
|
1526
1860
|
var installCommand = new Command("install").description("Install Docker and configure system prerequisites").action(async () => {
|
|
1527
1861
|
const spinner = createSpinner("");
|
|
1528
1862
|
log.step("Checking Docker installation...");
|
|
@@ -1568,152 +1902,12 @@ var installCommand = new Command("install").description("Install Docker and conf
|
|
|
1568
1902
|
init_banner();
|
|
1569
1903
|
import { Command as Command2 } from "commander";
|
|
1570
1904
|
import chalk7 from "chalk";
|
|
1905
|
+
init_logger();
|
|
1571
1906
|
|
|
1572
1907
|
// src/ui/prompts.ts
|
|
1908
|
+
init_dlp();
|
|
1573
1909
|
import { confirm, password } from "@inquirer/prompts";
|
|
1574
1910
|
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");
|
|
1599
|
-
}
|
|
1600
|
-
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
1601
|
-
}
|
|
1602
|
-
function shellCommand(strings, ...values) {
|
|
1603
|
-
let result = strings[0];
|
|
1604
|
-
for (let i = 0; i < values.length; i++) {
|
|
1605
|
-
result += shellEscape(values[i]) + strings[i + 1];
|
|
1606
|
-
}
|
|
1607
|
-
return result;
|
|
1608
|
-
}
|
|
1609
|
-
function redact(text) {
|
|
1610
|
-
let result = text;
|
|
1611
|
-
for (const pattern of SECRET_PATTERNS) {
|
|
1612
|
-
pattern.lastIndex = 0;
|
|
1613
|
-
result = result.replace(pattern, "[REDACTED]");
|
|
1614
|
-
}
|
|
1615
|
-
return result;
|
|
1616
|
-
}
|
|
1617
|
-
function redactError(err) {
|
|
1618
|
-
if (err instanceof Error) {
|
|
1619
|
-
const safe = new Error(redact(err.message));
|
|
1620
|
-
safe.name = err.name;
|
|
1621
|
-
if (err.stack) safe.stack = redact(err.stack);
|
|
1622
|
-
return safe;
|
|
1623
|
-
}
|
|
1624
|
-
return new Error(redact(String(err)));
|
|
1625
|
-
}
|
|
1626
|
-
var DlpViolation = class extends Error {
|
|
1627
|
-
constructor(message) {
|
|
1628
|
-
super(message);
|
|
1629
|
-
this.name = "DlpViolation";
|
|
1630
|
-
}
|
|
1631
|
-
};
|
|
1632
|
-
function validateKeyValue(keyName, value) {
|
|
1633
|
-
if (FORBIDDEN_KEY_CHARS.test(value)) {
|
|
1634
|
-
throw new DlpViolation(
|
|
1635
|
-
`${keyName} contains characters that are not permitted in API keys`
|
|
1636
|
-
);
|
|
1637
|
-
}
|
|
1638
|
-
if (value.length < 10 || value.length > 256) {
|
|
1639
|
-
throw new DlpViolation(
|
|
1640
|
-
`${keyName} length out of expected range (10-256 characters)`
|
|
1641
|
-
);
|
|
1642
|
-
}
|
|
1643
|
-
const validator = KEY_VALIDATORS[keyName];
|
|
1644
|
-
if (validator && !validator.test(value)) {
|
|
1645
|
-
throw new DlpViolation(
|
|
1646
|
-
`${keyName} does not match the expected format for this key type`
|
|
1647
|
-
);
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
function validateAllKeys(keys) {
|
|
1651
|
-
const violations = [];
|
|
1652
|
-
for (const [name, value] of Object.entries(keys)) {
|
|
1653
|
-
if (!value) continue;
|
|
1654
|
-
try {
|
|
1655
|
-
validateKeyValue(name, value);
|
|
1656
|
-
} catch (err) {
|
|
1657
|
-
if (err instanceof DlpViolation) violations.push(err.message);
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
return violations;
|
|
1661
|
-
}
|
|
1662
|
-
var HMAC_PATH = path4.join(NEXUS_HOME, ".keys.hmac");
|
|
1663
|
-
function computeFileHmac(filePath, secret) {
|
|
1664
|
-
const content = fs2.readFileSync(filePath);
|
|
1665
|
-
return crypto.createHmac("sha256", secret).update(content).digest("hex");
|
|
1666
|
-
}
|
|
1667
|
-
function sealKeysFile(keysPath, masterSecret) {
|
|
1668
|
-
const hmac = computeFileHmac(keysPath, masterSecret);
|
|
1669
|
-
fs2.writeFileSync(HMAC_PATH, hmac, { mode: 384 });
|
|
1670
|
-
}
|
|
1671
|
-
function verifyKeysFile(keysPath, masterSecret) {
|
|
1672
|
-
const keysExist = fs2.existsSync(keysPath);
|
|
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
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
var AUDIT_PATH = path4.join(NEXUS_HOME, "audit.log");
|
|
1688
|
-
var MAX_AUDIT_SIZE = 10 * 1024 * 1024;
|
|
1689
|
-
function audit(event, detail = "") {
|
|
1690
|
-
try {
|
|
1691
|
-
const dir = path4.dirname(AUDIT_PATH);
|
|
1692
|
-
if (!fs2.existsSync(dir)) return;
|
|
1693
|
-
if (fs2.existsSync(AUDIT_PATH)) {
|
|
1694
|
-
const stat = fs2.statSync(AUDIT_PATH);
|
|
1695
|
-
if (stat.size > MAX_AUDIT_SIZE) {
|
|
1696
|
-
const rotated = AUDIT_PATH + ".1";
|
|
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 });
|
|
1708
|
-
try {
|
|
1709
|
-
fs2.chmodSync(AUDIT_PATH, 384);
|
|
1710
|
-
} catch {
|
|
1711
|
-
}
|
|
1712
|
-
} catch {
|
|
1713
|
-
}
|
|
1714
|
-
}
|
|
1715
|
-
|
|
1716
|
-
// src/ui/prompts.ts
|
|
1717
1911
|
async function promptInitConfig() {
|
|
1718
1912
|
console.log(chalk6.bold("\n API Keys\n"));
|
|
1719
1913
|
const anthropicKey = await password({
|
|
@@ -1768,66 +1962,14 @@ async function promptInitConfig() {
|
|
|
1768
1962
|
};
|
|
1769
1963
|
}
|
|
1770
1964
|
|
|
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
|
-
}
|
|
1965
|
+
// src/commands/init.ts
|
|
1966
|
+
init_platform();
|
|
1967
|
+
init_secrets();
|
|
1968
|
+
init_docker();
|
|
1829
1969
|
|
|
1830
1970
|
// src/core/tunnel.ts
|
|
1971
|
+
init_docker();
|
|
1972
|
+
init_dlp();
|
|
1831
1973
|
var CLOUDFLARED_VERSION = "2024.12.2";
|
|
1832
1974
|
var CLOUDFLARED_SHA256 = {
|
|
1833
1975
|
amd64: "40ec9a0f5b58e3b04183aaf01c4ddd4dbc6af39b0f06be4b7ce8b1011d0a07ab",
|
|
@@ -1871,6 +2013,7 @@ async function startTunnel() {
|
|
|
1871
2013
|
}
|
|
1872
2014
|
|
|
1873
2015
|
// src/commands/init.ts
|
|
2016
|
+
init_dlp();
|
|
1874
2017
|
async function withSpinner(spinner, label, fn) {
|
|
1875
2018
|
spinner.text = label;
|
|
1876
2019
|
spinner.start();
|
|
@@ -1903,7 +2046,6 @@ var phases = [
|
|
|
1903
2046
|
{
|
|
1904
2047
|
name: "Configuration",
|
|
1905
2048
|
run: async (ctx) => {
|
|
1906
|
-
showBanner();
|
|
1907
2049
|
const platform = detectPlatform();
|
|
1908
2050
|
log.detail("Platform", `${platform.os} ${platform.arch}`);
|
|
1909
2051
|
const userConfig = await promptInitConfig();
|
|
@@ -2086,8 +2228,12 @@ var initCommand = new Command2("init").description("Scaffold and launch a new NE
|
|
|
2086
2228
|
|
|
2087
2229
|
// src/commands/start.ts
|
|
2088
2230
|
import { Command as Command3 } from "commander";
|
|
2231
|
+
init_logger();
|
|
2232
|
+
init_secrets();
|
|
2233
|
+
init_docker();
|
|
2089
2234
|
|
|
2090
2235
|
// src/core/health.ts
|
|
2236
|
+
init_docker();
|
|
2091
2237
|
async function waitForServer(timeoutMs = 9e5) {
|
|
2092
2238
|
const start = Date.now();
|
|
2093
2239
|
let lastLog = 0;
|
|
@@ -2135,8 +2281,13 @@ var startCommand = new Command3("start").description("Start the NEXUS runtime").
|
|
|
2135
2281
|
succeed(spinner, "Image ready");
|
|
2136
2282
|
spinner = createSpinner("Starting NEXUS container...");
|
|
2137
2283
|
spinner.start();
|
|
2284
|
+
const keys = loadKeys();
|
|
2285
|
+
if (!keys) {
|
|
2286
|
+
fail(spinner, "No API keys found. Run: buildwithnexus init");
|
|
2287
|
+
process.exit(1);
|
|
2288
|
+
}
|
|
2138
2289
|
await startNexus(
|
|
2139
|
-
{ anthropic:
|
|
2290
|
+
{ anthropic: keys.ANTHROPIC_API_KEY, openai: keys.OPENAI_API_KEY || "" },
|
|
2140
2291
|
{ port: config.httpPort }
|
|
2141
2292
|
);
|
|
2142
2293
|
succeed(spinner, "Container started");
|
|
@@ -2163,6 +2314,7 @@ var startCommand = new Command3("start").description("Start the NEXUS runtime").
|
|
|
2163
2314
|
|
|
2164
2315
|
// src/commands/stop.ts
|
|
2165
2316
|
import { Command as Command4 } from "commander";
|
|
2317
|
+
init_logger();
|
|
2166
2318
|
import { execFile } from "child_process";
|
|
2167
2319
|
import { promisify } from "util";
|
|
2168
2320
|
var execFileAsync = promisify(execFile);
|
|
@@ -2220,6 +2372,9 @@ var stopCommand = new Command4("stop").description("Gracefully shut down the NEX
|
|
|
2220
2372
|
});
|
|
2221
2373
|
|
|
2222
2374
|
// src/commands/status.ts
|
|
2375
|
+
init_logger();
|
|
2376
|
+
init_secrets();
|
|
2377
|
+
init_docker();
|
|
2223
2378
|
import { Command as Command5 } from "commander";
|
|
2224
2379
|
import chalk8 from "chalk";
|
|
2225
2380
|
async function checkHttpHealth(port) {
|
|
@@ -2294,10 +2449,14 @@ var statusCommand = new Command5("status").description("Check NEXUS runtime heal
|
|
|
2294
2449
|
});
|
|
2295
2450
|
|
|
2296
2451
|
// src/commands/doctor.ts
|
|
2452
|
+
init_logger();
|
|
2453
|
+
init_platform();
|
|
2454
|
+
init_docker();
|
|
2455
|
+
init_secrets();
|
|
2297
2456
|
import { Command as Command6 } from "commander";
|
|
2298
2457
|
import chalk9 from "chalk";
|
|
2299
|
-
import
|
|
2300
|
-
import
|
|
2458
|
+
import fs3 from "fs";
|
|
2459
|
+
import path5 from "path";
|
|
2301
2460
|
var doctorCommand = new Command6("doctor").description("Diagnose NEXUS runtime environment").action(async () => {
|
|
2302
2461
|
const platform = detectPlatform();
|
|
2303
2462
|
const check = (ok) => ok ? chalk9.green("\u2713") : chalk9.red("\u2717");
|
|
@@ -2313,7 +2472,7 @@ var doctorCommand = new Command6("doctor").description("Diagnose NEXUS runtime e
|
|
|
2313
2472
|
} else {
|
|
2314
2473
|
console.log(` ${check(false)} Docker not installed`);
|
|
2315
2474
|
}
|
|
2316
|
-
const keyExists =
|
|
2475
|
+
const keyExists = fs3.existsSync(path5.join(NEXUS_HOME2, "ssh", "id_nexus_vm"));
|
|
2317
2476
|
console.log(` ${check(keyExists)} SSH key`);
|
|
2318
2477
|
const config = loadConfig();
|
|
2319
2478
|
console.log(` ${check(!!config)} Configuration`);
|
|
@@ -2345,6 +2504,7 @@ var doctorCommand = new Command6("doctor").description("Diagnose NEXUS runtime e
|
|
|
2345
2504
|
});
|
|
2346
2505
|
|
|
2347
2506
|
// src/commands/logs.ts
|
|
2507
|
+
init_logger();
|
|
2348
2508
|
import { Command as Command7 } from "commander";
|
|
2349
2509
|
import { execa as execa3 } from "execa";
|
|
2350
2510
|
var logsCommand = new Command7("logs").description("View NEXUS server logs").action(async () => {
|
|
@@ -2378,16 +2538,19 @@ var logsCommand = new Command7("logs").description("View NEXUS server logs").act
|
|
|
2378
2538
|
|
|
2379
2539
|
// src/commands/update.ts
|
|
2380
2540
|
import { Command as Command8 } from "commander";
|
|
2381
|
-
import
|
|
2382
|
-
import
|
|
2383
|
-
import { fileURLToPath as
|
|
2541
|
+
import path6 from "path";
|
|
2542
|
+
import fs4 from "fs";
|
|
2543
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2544
|
+
init_logger();
|
|
2545
|
+
init_secrets();
|
|
2546
|
+
init_docker();
|
|
2384
2547
|
import { execa as execa4 } from "execa";
|
|
2385
2548
|
function getReleaseTarball() {
|
|
2386
|
-
const dir =
|
|
2387
|
-
const tarballPath =
|
|
2388
|
-
if (
|
|
2389
|
-
const rootPath =
|
|
2390
|
-
if (
|
|
2549
|
+
const dir = path6.dirname(fileURLToPath2(import.meta.url));
|
|
2550
|
+
const tarballPath = path6.join(dir, "nexus-release.tar.gz");
|
|
2551
|
+
if (fs4.existsSync(tarballPath)) return tarballPath;
|
|
2552
|
+
const rootPath = path6.resolve(dir, "..", "dist", "nexus-release.tar.gz");
|
|
2553
|
+
if (fs4.existsSync(rootPath)) return rootPath;
|
|
2391
2554
|
throw new Error("nexus-release.tar.gz not found. Reinstall buildwithnexus to get the latest release.");
|
|
2392
2555
|
}
|
|
2393
2556
|
var updateCommand = new Command8("update").description("Update NEXUS to the latest bundled release and restart").action(async () => {
|
|
@@ -2438,9 +2601,12 @@ var updateCommand = new Command8("update").description("Update NEXUS to the late
|
|
|
2438
2601
|
// src/commands/destroy.ts
|
|
2439
2602
|
import { Command as Command9 } from "commander";
|
|
2440
2603
|
import chalk10 from "chalk";
|
|
2441
|
-
import
|
|
2604
|
+
import fs5 from "fs";
|
|
2442
2605
|
import { input } from "@inquirer/prompts";
|
|
2443
|
-
|
|
2606
|
+
init_logger();
|
|
2607
|
+
init_secrets();
|
|
2608
|
+
init_docker();
|
|
2609
|
+
import path7 from "path";
|
|
2444
2610
|
var destroyCommand = new Command9("destroy").description("Remove NEXUS VM and all data").option("--force", "Skip confirmation").action(async (opts) => {
|
|
2445
2611
|
const config = loadConfig();
|
|
2446
2612
|
if (!opts.force) {
|
|
@@ -2467,9 +2633,9 @@ var destroyCommand = new Command9("destroy").description("Remove NEXUS VM and al
|
|
|
2467
2633
|
}
|
|
2468
2634
|
await stopNexus();
|
|
2469
2635
|
}
|
|
2470
|
-
const sshConfigPath =
|
|
2471
|
-
if (
|
|
2472
|
-
const content =
|
|
2636
|
+
const sshConfigPath = path7.join(process.env.HOME || "~", ".ssh", "config");
|
|
2637
|
+
if (fs5.existsSync(sshConfigPath)) {
|
|
2638
|
+
const content = fs5.readFileSync(sshConfigPath, "utf-8");
|
|
2473
2639
|
const lines = content.split("\n");
|
|
2474
2640
|
const filtered = [];
|
|
2475
2641
|
let skip = false;
|
|
@@ -2482,14 +2648,17 @@ var destroyCommand = new Command9("destroy").description("Remove NEXUS VM and al
|
|
|
2482
2648
|
skip = false;
|
|
2483
2649
|
filtered.push(line);
|
|
2484
2650
|
}
|
|
2485
|
-
|
|
2651
|
+
fs5.writeFileSync(sshConfigPath, filtered.join("\n"));
|
|
2486
2652
|
}
|
|
2487
|
-
|
|
2653
|
+
fs5.rmSync(NEXUS_HOME2, { recursive: true, force: true });
|
|
2488
2654
|
succeed(spinner, "NEXUS runtime destroyed");
|
|
2489
2655
|
log.dim("Run 'buildwithnexus init' to set up again");
|
|
2490
2656
|
});
|
|
2491
2657
|
|
|
2492
2658
|
// src/commands/keys.ts
|
|
2659
|
+
init_logger();
|
|
2660
|
+
init_secrets();
|
|
2661
|
+
init_dlp();
|
|
2493
2662
|
import { Command as Command10 } from "commander";
|
|
2494
2663
|
import { password as password2 } from "@inquirer/prompts";
|
|
2495
2664
|
import chalk11 from "chalk";
|
|
@@ -2548,6 +2717,8 @@ keysCommand.command("set <key>").description("Set or update an API key (e.g. ANT
|
|
|
2548
2717
|
});
|
|
2549
2718
|
|
|
2550
2719
|
// src/commands/ssh.ts
|
|
2720
|
+
init_logger();
|
|
2721
|
+
init_docker();
|
|
2551
2722
|
import { Command as Command11 } from "commander";
|
|
2552
2723
|
import { execa as execa5 } from "execa";
|
|
2553
2724
|
var sshCommand = new Command11("ssh").description("Open an interactive shell inside the NEXUS container").action(async () => {
|
|
@@ -2561,9 +2732,13 @@ var sshCommand = new Command11("ssh").description("Open an interactive shell ins
|
|
|
2561
2732
|
});
|
|
2562
2733
|
|
|
2563
2734
|
// src/commands/brainstorm.ts
|
|
2735
|
+
init_logger();
|
|
2564
2736
|
import { Command as Command12 } from "commander";
|
|
2565
2737
|
import chalk12 from "chalk";
|
|
2566
2738
|
import { input as input2 } from "@inquirer/prompts";
|
|
2739
|
+
init_secrets();
|
|
2740
|
+
init_docker();
|
|
2741
|
+
init_dlp();
|
|
2567
2742
|
var COS_PREFIX = chalk12.bold.cyan(" Chief of Staff");
|
|
2568
2743
|
var YOU_PREFIX = chalk12.bold.white(" You");
|
|
2569
2744
|
var DIVIDER = chalk12.dim(" " + "\u2500".repeat(56));
|
|
@@ -2682,9 +2857,13 @@ var brainstormCommand = new Command12("brainstorm").description("Brainstorm an i
|
|
|
2682
2857
|
});
|
|
2683
2858
|
|
|
2684
2859
|
// src/commands/ninety-nine.ts
|
|
2860
|
+
init_logger();
|
|
2685
2861
|
import { Command as Command13 } from "commander";
|
|
2686
2862
|
import chalk13 from "chalk";
|
|
2687
2863
|
import { input as input3 } from "@inquirer/prompts";
|
|
2864
|
+
init_secrets();
|
|
2865
|
+
init_docker();
|
|
2866
|
+
init_dlp();
|
|
2688
2867
|
var AGENT_PREFIX = chalk13.bold.green(" 99 \u276F");
|
|
2689
2868
|
var YOU_PREFIX2 = chalk13.bold.white(" You");
|
|
2690
2869
|
var DIVIDER2 = chalk13.dim(" " + "\u2500".repeat(56));
|
|
@@ -2872,15 +3051,20 @@ var ninetyNineCommand = new Command13("99").description("AI pair-programming ses
|
|
|
2872
3051
|
});
|
|
2873
3052
|
|
|
2874
3053
|
// src/commands/shell.ts
|
|
3054
|
+
init_logger();
|
|
2875
3055
|
import { Command as Command14 } from "commander";
|
|
2876
3056
|
import chalk16 from "chalk";
|
|
3057
|
+
init_secrets();
|
|
3058
|
+
init_docker();
|
|
3059
|
+
init_dlp();
|
|
2877
3060
|
|
|
2878
3061
|
// src/ui/repl.ts
|
|
3062
|
+
init_secrets();
|
|
2879
3063
|
import readline3 from "readline";
|
|
2880
|
-
import
|
|
2881
|
-
import
|
|
3064
|
+
import fs6 from "fs";
|
|
3065
|
+
import path8 from "path";
|
|
2882
3066
|
import chalk14 from "chalk";
|
|
2883
|
-
var HISTORY_FILE =
|
|
3067
|
+
var HISTORY_FILE = path8.join(NEXUS_HOME2, "shell_history");
|
|
2884
3068
|
var MAX_HISTORY = 1e3;
|
|
2885
3069
|
var Repl = class {
|
|
2886
3070
|
rl = null;
|
|
@@ -2896,17 +3080,17 @@ var Repl = class {
|
|
|
2896
3080
|
}
|
|
2897
3081
|
loadHistory() {
|
|
2898
3082
|
try {
|
|
2899
|
-
if (
|
|
2900
|
-
this.history =
|
|
3083
|
+
if (fs6.existsSync(HISTORY_FILE)) {
|
|
3084
|
+
this.history = fs6.readFileSync(HISTORY_FILE, "utf-8").split("\n").filter(Boolean).slice(-MAX_HISTORY);
|
|
2901
3085
|
}
|
|
2902
3086
|
} catch {
|
|
2903
3087
|
}
|
|
2904
3088
|
}
|
|
2905
3089
|
saveHistory() {
|
|
2906
3090
|
try {
|
|
2907
|
-
const dir =
|
|
2908
|
-
|
|
2909
|
-
|
|
3091
|
+
const dir = path8.dirname(HISTORY_FILE);
|
|
3092
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
3093
|
+
fs6.writeFileSync(HISTORY_FILE, this.history.slice(-MAX_HISTORY).join("\n") + "\n", { mode: 384 });
|
|
2910
3094
|
} catch {
|
|
2911
3095
|
}
|
|
2912
3096
|
}
|
|
@@ -3002,6 +3186,9 @@ var Repl = class {
|
|
|
3002
3186
|
};
|
|
3003
3187
|
|
|
3004
3188
|
// src/core/event-stream.ts
|
|
3189
|
+
init_docker();
|
|
3190
|
+
init_secrets();
|
|
3191
|
+
init_dlp();
|
|
3005
3192
|
import chalk15 from "chalk";
|
|
3006
3193
|
var ROLE_COLORS = {
|
|
3007
3194
|
"Chief of Staff": chalk15.bold.cyan,
|
|
@@ -3116,8 +3303,8 @@ var EventStream = class {
|
|
|
3116
3303
|
};
|
|
3117
3304
|
|
|
3118
3305
|
// src/commands/shell.ts
|
|
3119
|
-
async function httpPost(httpPort,
|
|
3120
|
-
const res = await fetch(`http://localhost:${httpPort}${
|
|
3306
|
+
async function httpPost(httpPort, path11, body) {
|
|
3307
|
+
const res = await fetch(`http://localhost:${httpPort}${path11}`, {
|
|
3121
3308
|
method: "POST",
|
|
3122
3309
|
headers: { "Content-Type": "application/json" },
|
|
3123
3310
|
body: JSON.stringify(body),
|
|
@@ -3132,9 +3319,9 @@ async function httpPost(httpPort, path12, body) {
|
|
|
3132
3319
|
return text;
|
|
3133
3320
|
}
|
|
3134
3321
|
}
|
|
3135
|
-
async function httpGet(httpPort,
|
|
3322
|
+
async function httpGet(httpPort, path11) {
|
|
3136
3323
|
try {
|
|
3137
|
-
const res = await fetch(`http://localhost:${httpPort}${
|
|
3324
|
+
const res = await fetch(`http://localhost:${httpPort}${path11}`, {
|
|
3138
3325
|
signal: AbortSignal.timeout(1e4)
|
|
3139
3326
|
});
|
|
3140
3327
|
const text = await res.text();
|
|
@@ -3551,49 +3738,19 @@ var shellCommand2 = new Command14("shell").description("Launch the interactive N
|
|
|
3551
3738
|
}
|
|
3552
3739
|
});
|
|
3553
3740
|
|
|
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
3741
|
// src/core/update-notifier.ts
|
|
3585
|
-
import
|
|
3586
|
-
import
|
|
3587
|
-
import
|
|
3742
|
+
import fs7 from "fs";
|
|
3743
|
+
import path9 from "path";
|
|
3744
|
+
import os3 from "os";
|
|
3588
3745
|
import https from "https";
|
|
3589
3746
|
import chalk17 from "chalk";
|
|
3590
3747
|
var PACKAGE_NAME = "buildwithnexus";
|
|
3591
3748
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
3592
|
-
var STATE_DIR =
|
|
3593
|
-
var STATE_FILE =
|
|
3749
|
+
var STATE_DIR = path9.join(os3.homedir(), ".buildwithnexus");
|
|
3750
|
+
var STATE_FILE = path9.join(STATE_DIR, ".update-check.json");
|
|
3594
3751
|
function readState() {
|
|
3595
3752
|
try {
|
|
3596
|
-
const raw =
|
|
3753
|
+
const raw = fs7.readFileSync(STATE_FILE, "utf-8");
|
|
3597
3754
|
return JSON.parse(raw);
|
|
3598
3755
|
} catch {
|
|
3599
3756
|
return { lastCheck: 0, latestVersion: null };
|
|
@@ -3601,8 +3758,8 @@ function readState() {
|
|
|
3601
3758
|
}
|
|
3602
3759
|
function writeState(state) {
|
|
3603
3760
|
try {
|
|
3604
|
-
|
|
3605
|
-
|
|
3761
|
+
fs7.mkdirSync(STATE_DIR, { recursive: true, mode: 448 });
|
|
3762
|
+
fs7.writeFileSync(STATE_FILE, JSON.stringify(state), { mode: 384 });
|
|
3606
3763
|
} catch {
|
|
3607
3764
|
}
|
|
3608
3765
|
}
|
|
@@ -3674,18 +3831,34 @@ function printUpdateBanner(current, latest) {
|
|
|
3674
3831
|
process.stderr.write(msg + "\n");
|
|
3675
3832
|
}
|
|
3676
3833
|
|
|
3834
|
+
// src/core/models.ts
|
|
3835
|
+
var MODELS = {
|
|
3836
|
+
OPUS: "claude-opus-4-6",
|
|
3837
|
+
SONNET: "claude-sonnet-4-6-20250929",
|
|
3838
|
+
HAIKU: "claude-haiku-4-5-20251001",
|
|
3839
|
+
DEFAULT: "claude-sonnet-4-6-20250929"
|
|
3840
|
+
};
|
|
3841
|
+
|
|
3677
3842
|
// src/bin.ts
|
|
3678
3843
|
import dotenv2 from "dotenv";
|
|
3679
|
-
import
|
|
3680
|
-
import
|
|
3681
|
-
var homeEnvPath =
|
|
3844
|
+
import os4 from "os";
|
|
3845
|
+
import path10 from "path";
|
|
3846
|
+
var homeEnvPath = path10.join(os4.homedir(), ".env.local");
|
|
3682
3847
|
dotenv2.config({ path: homeEnvPath });
|
|
3683
|
-
var version = true ? "0.7.
|
|
3848
|
+
var version = true ? "0.7.2" : pkg.version;
|
|
3684
3849
|
checkForUpdates(version);
|
|
3685
3850
|
program.name("buildwithnexus").description("Nexus - AI-Powered Task Execution").version(version);
|
|
3686
3851
|
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",
|
|
3852
|
+
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
3853
|
program.command("dashboard").description("Start the Nexus dashboard server").option("-p, --port <port>", "Dashboard port", "4201").action(dashboardCommand);
|
|
3854
|
+
program.command("server").description("Start the Nexus backend server").action(async () => {
|
|
3855
|
+
const { startBackend: startBackend2 } = await Promise.resolve().then(() => (init_docker(), docker_exports));
|
|
3856
|
+
await startBackend2();
|
|
3857
|
+
const chalk18 = (await import("chalk")).default;
|
|
3858
|
+
console.log(chalk18.green("Backend server started. Press Ctrl+C to stop."));
|
|
3859
|
+
await new Promise(() => {
|
|
3860
|
+
});
|
|
3861
|
+
});
|
|
3689
3862
|
program.command("da-status").description("Check Nexus backend status").action(async () => {
|
|
3690
3863
|
const backendUrl = process.env.BACKEND_URL || "http://localhost:4200";
|
|
3691
3864
|
try {
|
|
@@ -3703,12 +3876,20 @@ program.command("da-status").description("Check Nexus backend status").action(as
|
|
|
3703
3876
|
console.log(" cd ~/Projects/nexus && python -m src.deep_agents_server");
|
|
3704
3877
|
}
|
|
3705
3878
|
});
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3879
|
+
program.addCommand(installCommand);
|
|
3880
|
+
program.addCommand(initCommand);
|
|
3881
|
+
program.addCommand(startCommand);
|
|
3882
|
+
program.addCommand(stopCommand);
|
|
3883
|
+
program.addCommand(statusCommand);
|
|
3884
|
+
program.addCommand(doctorCommand);
|
|
3885
|
+
program.addCommand(logsCommand);
|
|
3886
|
+
program.addCommand(updateCommand);
|
|
3887
|
+
program.addCommand(destroyCommand);
|
|
3888
|
+
program.addCommand(keysCommand);
|
|
3889
|
+
program.addCommand(sshCommand);
|
|
3890
|
+
program.addCommand(brainstormCommand);
|
|
3891
|
+
program.addCommand(ninetyNineCommand);
|
|
3892
|
+
program.addCommand(shellCommand2);
|
|
3712
3893
|
if (!process.argv.slice(2).length) {
|
|
3713
3894
|
interactiveMode().catch((err) => {
|
|
3714
3895
|
console.error(err);
|
|
@@ -3717,3 +3898,6 @@ if (!process.argv.slice(2).length) {
|
|
|
3717
3898
|
} else {
|
|
3718
3899
|
program.parse();
|
|
3719
3900
|
}
|
|
3901
|
+
export {
|
|
3902
|
+
version
|
|
3903
|
+
};
|