buildwithnexus 0.7.1 → 0.7.3

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