bhooai-nexus 3.0.6 → 3.0.7

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.
Files changed (46) hide show
  1. package/package.json +2 -1
  2. package/packages/nexus-admin/package.json +1 -1
  3. package/packages/nexus-ads/package.json +1 -1
  4. package/packages/nexus-ai-client/package.json +1 -1
  5. package/packages/nexus-auth/package.json +1 -1
  6. package/packages/nexus-cache/package.json +1 -1
  7. package/packages/nexus-cli/package.json +2 -1
  8. package/packages/nexus-cli/src/browser.ts +4 -2
  9. package/packages/nexus-cli/src/commands/dev.ts +15 -3
  10. package/packages/nexus-cli/src/commands/doctor.ts +140 -29
  11. package/packages/nexus-cli/src/commands/init.ts +22 -4
  12. package/packages/nexus-cli/src/commands/license.ts +115 -31
  13. package/packages/nexus-cli/src/commands/sync.ts +310 -0
  14. package/packages/nexus-cli/src/commands/uninstall.ts +1002 -0
  15. package/packages/nexus-cli/src/devPanel.ts +88 -7
  16. package/packages/nexus-cli/src/devServiceManager.ts +1 -1
  17. package/packages/nexus-cli/src/dispatcher.ts +16 -50
  18. package/packages/nexus-cli/src/helpDoc.ts +207 -0
  19. package/packages/nexus-cli/src/index.ts +13 -11
  20. package/packages/nexus-cli/src/launcher.ts +31 -32
  21. package/packages/nexus-cli/src/wizard.ts +68 -11
  22. package/packages/nexus-cli/templates/base/dockerignore +1 -0
  23. package/packages/nexus-cli/templates/base/gitignore +2 -1
  24. package/packages/nexus-cli/templates/base/nexus/licence/.gitkeep +0 -0
  25. package/packages/nexus-cli/templates/base/nexus.config.ts.ejs +18 -9
  26. package/packages/nexus-core/package.json +1 -1
  27. package/packages/nexus-core/src/app/createNexusApp.ts +1 -1
  28. package/packages/nexus-core/src/config/defaults.ts +2 -2
  29. package/packages/nexus-core/src/config/schema.ts +3 -3
  30. package/packages/nexus-core/src/config/types.ts +2 -2
  31. package/packages/nexus-crypto/package.json +1 -1
  32. package/packages/nexus-crypto/src/license.ts +46 -16
  33. package/packages/nexus-data/package.json +1 -1
  34. package/packages/nexus-email/package.json +1 -1
  35. package/packages/nexus-examples/examples/ai-chat/apps/frontend/src/App.tsx +10 -1
  36. package/packages/nexus-examples/package.json +1 -1
  37. package/packages/nexus-fusion/package.json +1 -1
  38. package/packages/nexus-fusion-core/package.json +1 -1
  39. package/packages/nexus-future/package.json +1 -1
  40. package/packages/nexus-graphql/package.json +1 -1
  41. package/packages/nexus-payments/package.json +1 -1
  42. package/packages/nexus-plugins/package.json +1 -1
  43. package/packages/nexus-postcss/package.json +1 -1
  44. package/packages/nexus-realtime/package.json +1 -1
  45. package/packages/nexus-safe-goto/package.json +1 -1
  46. package/packages/nexus-telemetry/package.json +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bhooai-nexus",
3
- "version": "3.0.6",
3
+ "version": "3.0.7",
4
4
  "description": "BhooAI Nexus - a Node-based full-stack framework (websockets, GraphQL federation, inbuilt security, custom ODM, OAuth, payments, WebRTC, AI, plugins). v2: multi-app, Laravel-style folders, Docker-based scaling.",
5
5
  "license": "MIT",
6
6
  "author": "BhooAI - oravianando",
@@ -97,6 +97,7 @@
97
97
  "init": "node bin/nexus.js init",
98
98
  "doctor": "node bin/nexus.js doctor",
99
99
  "typecheck": "tsc --noEmit -p tsconfig.json",
100
+ "publish:packages": "node scripts/publish.mjs",
100
101
  "lint": "eslint \"packages/**/*.ts\" \"apps/**/*.{ts,tsx}\"",
101
102
  "format": "prettier --write ."
102
103
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bhooai/nexus-admin",
3
- "version": "2.0.18",
3
+ "version": "2.0.20",
4
4
  "description": "BhooAI Nexus v2 admin SPA — Apps-tab-first, live registry/health/logs/config/users/payments/AI",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bhooai/nexus-ads",
3
- "version": "2.0.16",
3
+ "version": "2.0.18",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bhooai/nexus-ai-client",
3
- "version": "2.0.16",
3
+ "version": "2.0.18",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bhooai/nexus-auth",
3
- "version": "2.0.16",
3
+ "version": "2.0.18",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bhooai/nexus-cache",
3
- "version": "2.0.16",
3
+ "version": "2.0.18",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bhooai/nexus-cli",
3
- "version": "2.0.18",
3
+ "version": "2.0.20",
4
4
  "description": "BhooAI Nexus v2 CLI — init, dev, build, test, scaffolding, plugins, queue, db.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -21,6 +21,7 @@
21
21
  "@bhooai/nexus-core": "^2.0.1",
22
22
  "@bhooai/nexus-crypto": "^2.0.1",
23
23
  "@bhooai/nexus-examples": "^2.0.1",
24
+ "mongodb": "^6.9.0",
24
25
  "tsx": "^4.19.0",
25
26
  "zod": "^3.23.8"
26
27
  },
@@ -7,8 +7,10 @@ import { spawn } from 'node:child_process';
7
7
  export function openBrowser(url: string): void {
8
8
  try {
9
9
  if (process.platform === 'win32') {
10
- // `start` is a cmd builtin; the empty "" is the window title.
11
- spawn('cmd', ['/c', 'start', '', url], { stdio: 'ignore', detached: true }).unref();
10
+ // `rundll32` receives the URL as a plain argv (no shell), so `&` in the
11
+ // query string is NOT treated as a command separator. `cmd /c start`
12
+ // would truncate at the first `&`, dropping state/code_challenge.
13
+ spawn('rundll32.exe', ['url.dll,FileProtocolHandler', url], { stdio: 'ignore', detached: true }).unref();
12
14
  } else if (process.platform === 'darwin') {
13
15
  spawn('open', [url], { stdio: 'ignore', detached: true }).unref();
14
16
  } else {
@@ -12,7 +12,7 @@ import { join, dirname } from 'node:path';
12
12
  import { fileURLToPath } from 'node:url';
13
13
  import type { CommandContext } from '../dispatcher.js';
14
14
  import { loadConfigAuto } from '@bhooai/nexus-core';
15
- import { ensureLicense } from '@bhooai/nexus-crypto';
15
+ import { ensureLicense, resolveLicenseToken } from '@bhooai/nexus-crypto';
16
16
  import { resolveConfiguredPorts, findFreeShift, describePortHolder } from '../ports.js';
17
17
  import { ServiceManager, type ServiceSpec } from '../devServiceManager.js';
18
18
  import { startDevPanel } from '../devPanel.js';
@@ -31,12 +31,19 @@ export async function run(ctx: CommandContext): Promise<void> {
31
31
  // check fails we still start the services: the backend boots in a
32
32
  // license-error state and answers requests with 402 LICENSE_REQUIRED, so the
33
33
  // UI can show the error instead of a proxy 500.
34
+ // The console's "Add license" banner is offered ONLY when no license token
35
+ // exists at all (missing nexus/licence/licence.jwt and no NEXUS_LICENSE_KEY) —
36
+ // transient verify failures (offline authority, expired token, …) just warn.
37
+ let licenseError: string | null = null;
34
38
  try {
35
39
  await ensureLicense({ projectDir: projectRoot });
36
40
  } catch (err) {
37
- console.warn(`⚠ License check failed: ${(err as Error).message}`);
41
+ licenseError = (err as Error).message;
42
+ console.warn(`⚠ License check failed: ${licenseError}`);
43
+ console.warn(' Run `nexus license add` to authorize this project.');
38
44
  console.warn(' Starting anyway — API requests will return 402 LICENSE_REQUIRED.');
39
45
  }
46
+ const licenseMissing = !resolveLicenseToken({ projectDir: projectRoot });
40
47
 
41
48
  const noPanel = !!ctx.args.flags['no-panel'];
42
49
  const isTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
@@ -47,6 +54,11 @@ export async function run(ctx: CommandContext): Promise<void> {
47
54
  return;
48
55
  }
49
56
 
57
+ // Expose the project root to every service so license resolution finds
58
+ // `<project>/nexus/licence/licence.jwt` even when an app's own projectRoot is a
59
+ // subfolder (e.g. apps/backend).
60
+ for (const s of specs) s.env = { ...(s.env ?? {}), NEXUS_PROJECT_ROOT: projectRoot };
61
+
50
62
  // Coordinated step-up: if any configured port is busy, shift the whole set
51
63
  // by the smallest delta that frees every port. nexus.config.ts stays
52
64
  // canonical — the shift is ephemeral for this run (NEXUS_PORT_STEP).
@@ -99,7 +111,7 @@ export async function run(ctx: CommandContext): Promise<void> {
99
111
  if (isTty && !noPanel) {
100
112
  // Full-screen Nexus Console.
101
113
  console.log(`nexus dev — ${specs.length} service(s). Opening console...\n`);
102
- await startDevPanel({ services: manager, onQuit: teardown });
114
+ await startDevPanel({ services: manager, onQuit: teardown, licenseError: licenseMissing ? licenseError : null });
103
115
  return;
104
116
  }
105
117
 
@@ -1,5 +1,7 @@
1
1
  /**
2
2
  * nexus doctor — environment health check. Never hard-fails; reports issues.
3
+ * Renders a full-screen TUI on a TTY (with a live spinner) and plain text when
4
+ * piped. `nexus doctor` and the welcome launcher's "Environment check" both use it.
3
5
  */
4
6
  import { exec } from 'node:child_process';
5
7
  import { promisify } from 'node:util';
@@ -7,11 +9,22 @@ import { existsSync } from 'node:fs';
7
9
  import { resolve } from 'node:path';
8
10
  import { createConnection } from 'node:net';
9
11
  import type { CommandContext } from '../dispatcher.js';
12
+ import { ANSI, Tui, isTty, getTermSize, type KeyInfo } from '../tui.js';
13
+ import { fitCell } from '../layout.js';
10
14
 
11
15
  const execAsync = promisify(exec);
12
16
 
17
+ export interface CheckResult {
18
+ name: string;
19
+ ok: boolean;
20
+ detail?: string;
21
+ /** Optional checks warn (•) but never count as failures. */
22
+ optional?: boolean;
23
+ }
24
+
13
25
  interface Check {
14
26
  name: string;
27
+ optional?: boolean;
15
28
  run(): Promise<{ ok: boolean; detail?: string }>;
16
29
  }
17
30
 
@@ -39,11 +52,8 @@ async function tcpReachable(host: string, port: number, timeoutMs: number = 1000
39
52
  });
40
53
  }
41
54
 
42
- export async function run(_ctx: CommandContext): Promise<void> {
43
- console.log('nexus doctor — environment check\n');
44
-
45
- const projectRoot = process.cwd();
46
- const checks: Check[] = [
55
+ function buildChecks(projectRoot: string): Check[] {
56
+ return [
47
57
  {
48
58
  name: 'Node.js ≥ 20.10',
49
59
  run: async () => {
@@ -66,50 +76,151 @@ export async function run(_ctx: CommandContext): Promise<void> {
66
76
  },
67
77
  {
68
78
  name: 'Python (optional — for AI server)',
79
+ optional: true,
69
80
  run: async () => {
70
81
  const v = await cmdVersion('python', ['--version']);
71
- return v ? { ok: true, detail: v } : { ok: false, detail: 'python not found (optional)' };
82
+ return v ? { ok: true, detail: v } : { ok: false, detail: 'python not found' };
72
83
  },
73
84
  },
74
85
  {
75
86
  name: 'Project layout (apps/backend)',
76
- run: async () => ({
77
- ok: existsSync(resolve(projectRoot, 'apps/backend')),
78
- detail: existsSync(resolve(projectRoot, 'apps/backend')) ? 'found' : 'apps/backend missing',
79
- }),
87
+ run: async () => {
88
+ const ok = existsSync(resolve(projectRoot, 'apps/backend'));
89
+ return { ok, detail: ok ? 'found' : 'apps/backend missing' };
90
+ },
80
91
  },
81
92
  {
82
93
  name: 'nexus.config.ts',
83
- run: async () => ({
84
- ok: existsSync(resolve(projectRoot, 'nexus.config.ts')),
85
- detail: existsSync(resolve(projectRoot, 'nexus.config.ts')) ? 'found' : 'missing',
86
- }),
94
+ run: async () => {
95
+ const ok = existsSync(resolve(projectRoot, 'nexus.config.ts'));
96
+ return { ok, detail: ok ? 'found' : 'missing' };
97
+ },
87
98
  },
88
99
  {
89
100
  name: 'MongoDB reachable (localhost:27017)',
90
- run: async () => ({
91
- ok: await tcpReachable('127.0.0.1', 27017),
92
- detail: (await tcpReachable('127.0.0.1', 27017)) ? 'ok' : 'not reachable',
93
- }),
101
+ run: async () => {
102
+ const ok = await tcpReachable('127.0.0.1', 27017);
103
+ return { ok, detail: ok ? 'ok' : 'not reachable' };
104
+ },
94
105
  },
95
106
  {
96
107
  name: 'Redis reachable (localhost:6379)',
97
- run: async () => ({
98
- ok: await tcpReachable('127.0.0.1', 6379),
99
- detail: (await tcpReachable('127.0.0.1', 6379)) ? 'ok' : 'not reachable (optional)',
100
- }),
108
+ optional: true,
109
+ run: async () => {
110
+ const ok = await tcpReachable('127.0.0.1', 6379);
111
+ return { ok, detail: ok ? 'ok' : 'not reachable' };
112
+ },
101
113
  },
102
114
  ];
115
+ }
103
116
 
104
- let failed = 0;
105
- for (const check of checks) {
106
- const result = await check.run().catch((e) => ({ ok: false, detail: String(e) }));
107
- const icon = result.ok ? '✓' : '✗';
108
- const detail = result.detail ? ` (${result.detail})` : '';
109
- console.log(`${icon} ${check.name}${detail}`);
110
- if (!result.ok) failed++;
117
+ /** Run every check and return structured results. */
118
+ export async function runChecks(projectRoot: string): Promise<CheckResult[]> {
119
+ const out: CheckResult[] = [];
120
+ for (const check of buildChecks(projectRoot)) {
121
+ const r = await check.run().catch((e) => ({ ok: false, detail: String(e) }));
122
+ out.push({ name: check.name, ok: r.ok, detail: r.detail, optional: check.optional });
111
123
  }
124
+ return out;
125
+ }
126
+
127
+ const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
128
+
129
+ function statusIcon(r: CheckResult): string {
130
+ if (r.ok) return `${ANSI.green}✓${ANSI.reset}`;
131
+ return r.optional ? `${ANSI.yellow}•${ANSI.reset}` : `${ANSI.red}✗${ANSI.reset}`;
132
+ }
112
133
 
134
+ function renderResults(results: CheckResult[]): string[] {
135
+ const W = Math.min(getTermSize().cols - 4, 84);
136
+ const nameW = Math.min(Math.max(20, ...results.map((r) => r.name.length)) + 2, 46);
137
+ const lines: string[] = [
138
+ `${ANSI.bold}${ANSI.cyan}nexus doctor${ANSI.reset} ${ANSI.gray}— environment check${ANSI.reset}`,
139
+ `${ANSI.gray}${'─'.repeat(Math.max(W, 10))}${ANSI.reset}`,
140
+ '',
141
+ ];
142
+ for (const r of results) {
143
+ const detail = r.detail ? ` ${ANSI.gray}(${r.detail})${ANSI.reset}` : '';
144
+ lines.push(` ${statusIcon(r)} ${fitCell(r.name, nameW)}${detail}`);
145
+ }
146
+ const failed = results.filter((r) => !r.ok && !r.optional).length;
147
+ const warned = results.filter((r) => !r.ok && r.optional).length;
148
+ lines.push('');
149
+ if (failed === 0) {
150
+ const extra = warned > 0 ? ` ${ANSI.gray}(${warned} optional unavailable)${ANSI.reset}` : '';
151
+ lines.push(` ${ANSI.green}All checks passed.${ANSI.reset}${extra}`);
152
+ } else {
153
+ lines.push(` ${ANSI.red}${failed} check(s) need attention.${ANSI.reset}`);
154
+ }
155
+ return lines;
156
+ }
157
+
158
+ /** Full-screen doctor TUI: spinner while running, `r` to rerun, `q` to close. */
159
+ async function runDoctorTui(projectRoot: string): Promise<void> {
160
+ let results: CheckResult[] = [];
161
+ let running = true;
162
+ let spin = 0;
163
+ let timer: ReturnType<typeof setInterval> | null = null;
164
+
165
+ const render = (tui: Tui): void => {
166
+ const lines: string[] = [
167
+ `${ANSI.bold}${ANSI.cyan}nexus doctor${ANSI.reset} ${ANSI.gray}— environment check${ANSI.reset}`,
168
+ `${ANSI.gray}${'─'.repeat(Math.max(Math.min(getTermSize().cols - 4, 84), 10))}${ANSI.reset}`,
169
+ '',
170
+ ];
171
+ if (running) {
172
+ lines.push(` ${ANSI.cyan}${SPINNER[spin % SPINNER.length]}${ANSI.reset} ${ANSI.gray}running checks…${ANSI.reset}`);
173
+ } else {
174
+ lines.push(...renderResults(results).slice(3));
175
+ }
176
+ const footer = `${ANSI.gray}${running ? 'please wait…' : 'r rerun · q close'}${ANSI.reset}`;
177
+ tui.draw(lines, footer);
178
+ };
179
+
180
+ const tui: Tui = new Tui((_str: string, key: KeyInfo) => {
181
+ if (key.name === 'q' || key.name === 'escape' || (key.name === 'c' && key.ctrl)) {
182
+ tui.quit = true;
183
+ return;
184
+ }
185
+ if (key.name === 'r' && !running) void refresh();
186
+ });
187
+
188
+ const refresh = async (): Promise<void> => {
189
+ running = true;
190
+ if (!timer) timer = setInterval(() => { spin++; render(tui); }, 90);
191
+ render(tui);
192
+ results = await runChecks(projectRoot);
193
+ running = false;
194
+ if (timer) { clearInterval(timer); timer = null; }
195
+ render(tui);
196
+ };
197
+
198
+ tui.enter();
199
+ const onResize = (): void => render(tui);
200
+ process.stdout.on('resize', onResize);
201
+ await refresh();
202
+ await tui.wait();
203
+ process.stdout.off('resize', onResize);
204
+ if (timer) clearInterval(timer);
205
+ tui.exit();
206
+ }
207
+
208
+ export async function run(_ctx: CommandContext): Promise<void> {
209
+ const projectRoot = process.cwd();
210
+
211
+ if (isTty()) {
212
+ await runDoctorTui(projectRoot);
213
+ return;
214
+ }
215
+
216
+ console.log('nexus doctor — environment check\n');
217
+ const results = await runChecks(projectRoot);
218
+ for (const r of results) {
219
+ const icon = r.ok ? '✓' : r.optional ? '•' : '✗';
220
+ const detail = r.detail ? ` (${r.detail})` : '';
221
+ console.log(`${icon} ${r.name}${detail}`);
222
+ }
223
+ const failed = results.filter((r) => !r.ok && !r.optional).length;
113
224
  console.log(failed === 0 ? '\nAll checks passed.' : `\n${failed} check(s) need attention.`);
114
225
  }
115
226
 
@@ -32,6 +32,7 @@ interface InitVars {
32
32
  name: string;
33
33
  nameSlug: string;
34
34
  jwtSecret: string;
35
+ dbActive: 'fusion' | 'mongodb';
35
36
  mongoUri: string;
36
37
  redisUrl: string;
37
38
  example: string;
@@ -41,6 +42,7 @@ interface InitVars {
41
42
  adminPort: number;
42
43
  aiPort: number;
43
44
  pyserverPort: number;
45
+ fusionPort: number;
44
46
  aiProviders: string[];
45
47
  /** Rendered `apps: [...]` block body (port layout) for nexus.config.ts. */
46
48
  appsBlock: string;
@@ -84,6 +86,7 @@ export async function run(ctx: CommandContext): Promise<void> {
84
86
  name,
85
87
  nameSlug: name,
86
88
  jwtSecret: randomSecret(32),
89
+ dbActive: (result as { dbActive?: 'fusion' | 'mongodb' }).dbActive ?? 'fusion',
87
90
  mongoUri: result.mongoUri,
88
91
  redisUrl: result.redisUrl,
89
92
  example: result.example,
@@ -93,6 +96,7 @@ export async function run(ctx: CommandContext): Promise<void> {
93
96
  adminPort: result.ports.admin,
94
97
  aiPort: result.ports.ai,
95
98
  pyserverPort: result.ports.pyserver,
99
+ fusionPort: result.ports.fusion,
96
100
  aiProviders: result.aiProviders,
97
101
  appsBlock: [
98
102
  ` { name: 'backend', port: ${result.ports.backend} },`,
@@ -100,6 +104,7 @@ export async function run(ctx: CommandContext): Promise<void> {
100
104
  ...(includeAdmin ? [` { name: 'admin', port: ${result.ports.admin} },`] : []),
101
105
  ...(includeAi ? [` { name: 'ai-server', port: ${result.ports.ai} },`] : []),
102
106
  ` { name: 'pyserver', port: ${result.ports.pyserver} },`,
107
+ ` { name: 'fusion', port: ${result.ports.fusion} },`,
103
108
  ].join('\n'),
104
109
  };
105
110
 
@@ -199,7 +204,7 @@ export async function run(ctx: CommandContext): Promise<void> {
199
204
  // 4. .env
200
205
  const envPath = join(targetDir, '.env');
201
206
  if (!existsSync(envPath)) {
202
- await writeFile(envPath, buildEnvFile(vars), 'utf-8');
207
+ await writeFile(envPath, buildEnvFile(vars, devMode), 'utf-8');
203
208
  }
204
209
 
205
210
  // 4b. --dev: rewrite bhooai-nexus + @bhooai/* to local file: + live symlink
@@ -253,7 +258,9 @@ Ports allocated:
253
258
  ${frontendLine}
254
259
  Admin : ${includeAdmin ? vars.adminPort : '(not included)'}
255
260
  AI server : ${includeAi ? vars.aiPort : '(not included)'}
256
- `);
261
+ PyServer : ${vars.pyserverPort}
262
+ Fusion DB : ${vars.fusionPort}
263
+ `);
257
264
 
258
265
  // Ensure stdin is paused so Node can drain naturally after TUI exit (even on npm failure → exit to shell).
259
266
  try { process.stdin.pause(); } catch {}
@@ -439,7 +446,7 @@ async function ensureBackendFolderTree(backendDir: string): Promise<void> {
439
446
  }
440
447
  }
441
448
 
442
- function buildEnvFile(vars: InitVars): string {
449
+ function buildEnvFile(vars: InitVars, dev = false): string {
443
450
  return `# BhooAI Nexus — generated .env
444
451
  # Secrets live here. Do not commit.
445
452
 
@@ -451,13 +458,20 @@ AI_PORT=${vars.aiPort}
451
458
  # Set this to the backend host when deploying to a VPS (default: localhost)
452
459
  # NEXUS_BACKEND_HOST=
453
460
 
454
- # Database
461
+ # Database — Fusion is the default (embedded, zero-dep).
462
+ # Set NEXUS_DB_ACTIVE=mongodb + NEXUS_DB_MONGODB_ENABLED=true to use Mongo.
463
+ NEXUS_DB_ACTIVE=${vars.dbActive}
455
464
  NEXUS_DB_URI=${vars.mongoUri}
456
465
  NEXUS_REDIS_URL=${vars.redisUrl}
457
466
 
458
467
  # Auth
459
468
  NEXUS_AUTH_JWT_SECRET=${vars.jwtSecret}
460
469
 
470
+ # License authority
471
+ ${dev
472
+ ? '# --dev: verify against the LOCAL authority (nexus-bhooai-com on :3000).\nNEXUS_LICENSE_SERVER=http://localhost:3000'
473
+ : '# Defaults to https://nexus.bhooai.com — override with NEXUS_LICENSE_SERVER.'}
474
+
461
475
  # AI (ollama is local by default; uncomment others as needed)
462
476
  NEXUS_AI_PROVIDER=ollama
463
477
  NEXUS_AI_MODEL=llama3.1:8b
@@ -469,6 +483,10 @@ ${vars.aiProviders.filter((p) => p !== 'ollama').map((p) => `# NEXUS_AI_${p.toUp
469
483
  PY_PORT=${vars.pyserverPort}
470
484
  NEXUS_IMAGES_URL=http://127.0.0.1:${vars.pyserverPort}
471
485
 
486
+ # Fusion DB server (nexus dev service "fusion", port from nexus.config.ts apps[])
487
+ FUSION_PORT=${vars.fusionPort}
488
+ NEXUS_FUSION_URL=http://127.0.0.1:${vars.fusionPort}
489
+
472
490
  # Storage (uncomment for S3)
473
491
  # NEXUS_STORAGE_S3_BUCKET=
474
492
  # NEXUS_STORAGE_S3_REGION=