nexus-prime 7.9.15 → 7.9.17

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/README.md CHANGED
@@ -16,8 +16,8 @@
16
16
  <p>
17
17
  <img src="https://img.shields.io/badge/Protocol-MCP-4285F4?style=for-the-badge" alt="MCP Protocol">
18
18
  <img src="https://img.shields.io/badge/license-Commercial-6f42c1?style=for-the-badge" alt="Commercial License">
19
- <a href="https://nexus-prime.cfd/pricing"><img src="https://img.shields.io/badge/30--day-free%20trial-00ff88?style=for-the-badge" alt="30-day free trial"></a>
20
- <img src="https://img.shields.io/badge/independent%20creators-free%20forever-ff69b4?style=for-the-badge" alt="Free for IC creators">
19
+ <a href="https://nexus-prime.cfd/pricing"><img src="https://img.shields.io/badge/3--day-grace%20then%20license-00ff88?style=for-the-badge" alt="3-day grace then license"></a>
20
+ <img src="https://img.shields.io/badge/licenses-reviewed%20within%2024h-ff69b4?style=for-the-badge" alt="Licenses reviewed within 24 hours">
21
21
  <img src="https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-444?style=for-the-badge" alt="Cross-platform">
22
22
  </p>
23
23
 
@@ -305,7 +305,7 @@ If any of that sounds like you — keep reading.
305
305
 
306
306
  <div align="center">
307
307
 
308
- **Free for 30 days. No credit card. No feature gating.**
308
+ **3-day no-license grace. Request a license before day 4. Manual approvals within 24 hours.**
309
309
 
310
310
  </div>
311
311
 
@@ -317,16 +317,16 @@ If any of that sounds like you — keep reading.
317
317
  <th>Caps</th>
318
318
  </tr>
319
319
  <tr>
320
- <td><b>🎁 Free Trial</b></td>
321
- <td>Everyone — try it risk-free</td>
322
- <td>Free for 30 days</td>
323
- <td>All Pro features</td>
320
+ <td><b>🎁 Local Grace</b></td>
321
+ <td>Everyone — install and verify locally</td>
322
+ <td>3 days without a license</td>
323
+ <td>Runtime available during grace; license required after day 3</td>
324
324
  </tr>
325
325
  <tr>
326
326
  <td><b>💎 Independent Creators</b></td>
327
327
  <td>Indie builders, solo engineers, students, OSS maintainers</td>
328
- <td><b>Free forever</b></td>
329
- <td>Unlimited usage on personal projects</td>
328
+ <td>Request access</td>
329
+ <td>Manual creator licenses reviewed by the team</td>
330
330
  </tr>
331
331
  <tr>
332
332
  <td><b>🚀 Pilot Program</b></td>
@@ -356,10 +356,10 @@ If any of that sounds like you — keep reading.
356
356
 
357
357
  ```bash
358
358
  nexus-prime license status # check your plan
359
- nexus-prime license activate <key> # activate any paid tier
359
+ nexus-prime license activate <key> # activate the key shared by the team
360
360
  ```
361
361
 
362
- → **[nexus-prime.cfd/pricing](https://nexus-prime.cfd/pricing)** · **[Apply to the pilot program](mailto:hello@nexus-prime.cfd?subject=Pilot%20Program)**
362
+ → **[nexus-prime.cfd/account](https://nexus-prime.cfd/account)** · **[Request by email](mailto:adarsh@nexus-prime.cfd?subject=Nexus%20Prime%20license%20request)**
363
363
 
364
364
  ---
365
365
 
@@ -380,19 +380,19 @@ No — it supercharges them. Keep the agents you already love. Nexus Prime adds
380
380
  <details>
381
381
  <summary><b>What happens after the 30-day trial ends?</b></summary>
382
382
  <br>
383
- You're never locked out. You can keep using a free tier, pick a paid plan, or if you're an independent creator apply for free-forever access. Your memory, data, and projects stay yours either way.
383
+ The local trial window ends and paid-tier work requires an active license. Your local memory and data stay on your machine; activate a license or request renewal from the account page to continue.
384
384
  </details>
385
385
 
386
386
  <details>
387
387
  <summary><b>Is it free for indie developers and creators?</b></summary>
388
388
  <br>
389
- Yes. If you build independently — solo founder, indie dev, content creator, student, OSS maintainer — you qualify for unlimited free usage. Just <a href="mailto:hello@nexus-prime.cfd?subject=Creator%20Program">email us</a> and we'll sort it.
389
+ Creator access is reviewed manually. If you build independently — solo founder, indie dev, content creator, student, OSS maintainer — request a creator license from the account page or email <a href="mailto:adarsh@nexus-prime.cfd?subject=Creator%20Program">adarsh@nexus-prime.cfd</a>.
390
390
  </details>
391
391
 
392
392
  <details>
393
393
  <summary><b>My team wants to try this across the whole engineering org. What's the pilot?</b></summary>
394
394
  <br>
395
- We're taking <b>10 companies</b> into our pilot program — full Team features free, white-glove onboarding, direct access to the team building it. First come, first served. <a href="mailto:hello@nexus-prime.cfd?subject=Pilot%20Program">Reach out</a>.
395
+ We're taking <b>10 companies</b> into our pilot program — white-glove onboarding, direct access to the team building it, and team licenses issued after review. <a href="mailto:adarsh@nexus-prime.cfd?subject=Pilot%20Program">Reach out</a>.
396
396
  </details>
397
397
 
398
398
  <details>
@@ -416,7 +416,7 @@ Each engineer runs their own local instance — nothing is shared across machine
416
416
  <details>
417
417
  <summary><b>What if I hit my plan's cap?</b></summary>
418
418
  <br>
419
- Nothing stops working. You get a gentle in-app nudge and a link to upgrade. Your data is never held hostage.
419
+ The app shows an upgrade request path and keeps your local data intact. Features above your licensed tier require an approved upgrade.
420
420
  </details>
421
421
 
422
422
  <details>
@@ -433,7 +433,7 @@ Nexus Prime was designed privacy-first, because the code on your machine is your
433
433
 
434
434
  - 🏠 **100% local.** All data lives on your disk, in your home directory.
435
435
  - 🚫 **No cloud sync.** Your code, your memory, your logs — never uploaded.
436
- - 🔐 **No required account.** You can run the free trial without signing up for anything.
436
+ - 🔐 **Short no-account grace.** You can run locally for 3 days without signing up; a license is required after that grace period.
437
437
  - 📊 **Telemetry is opt-in.** Off by default. When on, only aggregate, anonymous event counts are sent — never source code, never memory contents.
438
438
  - 🗑️ **Uninstall is clean.** Everything lives in one directory you can delete.
439
439
 
@@ -448,7 +448,8 @@ Nexus Prime was designed privacy-first, because the code on your machine is your
448
448
  | 💬 [**Discord**](https://discord.gg/tByGZgk5gS) | Real-time help, show-and-tell, feature ideas |
449
449
  | 🔴 [**Reddit — r/Nexus_Prime**](https://www.reddit.com/r/Nexus_Prime/) | Long-form posts, releases, community wins |
450
450
  | 🐦 [**X / Twitter**](https://x.com/nexusprime_ai) | Launch announcements, tips, updates |
451
- | 📧 [**hello@nexus-prime.cfd**](mailto:hello@nexus-prime.cfd) | Pilot program, creator program, enterprise, press |
451
+ | 📧 [**adarsh@nexus-prime.cfd**](mailto:adarsh@nexus-prime.cfd) | License requests, upgrades, pilots, enterprise |
452
+ | 📧 [**hello@nexus-prime.cfd**](mailto:hello@nexus-prime.cfd) | General support, press, community |
452
453
  | 🌐 [**nexus-prime.cfd**](https://nexus-prime.cfd) | Product site, demos, pricing, setup guides |
453
454
 
454
455
  </div>
@@ -457,11 +458,11 @@ Nexus Prime was designed privacy-first, because the code on your machine is your
457
458
 
458
459
  ## License
459
460
 
460
- Nexus Prime is a **commercial product** with generous free tiers:
461
+ Nexus Prime is a **commercial product** with manual license issuance:
461
462
 
462
- - ✅ Free for 30 days, for everyone
463
- - ✅ Free forever for independent creators
464
- - ✅ Free pilot for the first 10 partner companies
463
+ - ✅ 3-day local no-license grace, for everyone
464
+ - ✅ Manual creator, pilot, team, and enterprise license review
465
+ - ✅ License and upgrade requests reviewed within 24 hours
465
466
  - 💳 Paid plans for professional, team, and enterprise usage
466
467
 
467
468
  Commercial use without a valid license is prohibited. See [nexus-prime.cfd/pricing](https://nexus-prime.cfd/pricing) for current plans and terms.
@@ -38,11 +38,23 @@ export async function handleRuntimeGroup(toolName, hctx, request, args, ctx) {
38
38
  }
39
39
  // summary (default)
40
40
  const licStatus = getSharedLicenseManager().getStatus();
41
+ const trialLines = licStatus.trialPhase ? [
42
+ `- **Trial phase**: ${licStatus.trialPhase}`,
43
+ typeof licStatus.trialDaysRemaining === 'number'
44
+ ? `- **Trial remaining**: ${licStatus.trialDaysRemaining} day${licStatus.trialDaysRemaining === 1 ? '' : 's'}`
45
+ : '',
46
+ licStatus.activationRequired
47
+ ? `- **Agent motion**: License activation is required for paid-tier work; keep license/status tools available.`
48
+ : licStatus.trialPhase === 'activation'
49
+ ? `- **Agent motion**: Trial remains active; ask the user to request or activate a license soon, then keep working.`
50
+ : `- **Agent motion**: No license is mandatory during the first 3 days; ask the user to request a license soon.`,
51
+ ].filter((line) => Boolean(line)) : [];
41
52
  const lines = [
42
53
  `## License Status`,
43
54
  `- **Plan**: ${pcu.tier.toUpperCase()}`,
44
55
  `- **Status**: ${licStatus.valid ? 'Active' : licStatus.degradedReason ?? 'Inactive'}`,
45
56
  licStatus.expiresAt ? `- **Expires**: ${new Date(licStatus.expiresAt).toISOString().split('T')[0]}` : '- **Expires**: Never',
57
+ ...trialLines,
46
58
  '',
47
59
  `## Resource Usage`,
48
60
  `- Memory: ${pcu.memory.current.toLocaleString()} / ${Number.isFinite(pcu.memory.limit) ? pcu.memory.limit.toLocaleString() : '∞'} (${pcu.memory.pct}%)`,
@@ -54,6 +66,8 @@ export async function handleRuntimeGroup(toolName, hctx, request, args, ctx) {
54
66
  `## Upgrade`,
55
67
  `- Dashboard: http://127.0.0.1:3377 → License tab`,
56
68
  `- Web: https://nexus-prime.cfd/account`,
69
+ `- Email: adarsh@nexus-prime.cfd`,
70
+ `- SLA: team will share licenses within 24 hours`,
57
71
  `- CLI: nexus-prime license sync`,
58
72
  ];
59
73
  return { content: [{ type: 'text', text: lines.join('\n') }] };
@@ -154,6 +154,8 @@ export class SessionTelemetry {
154
154
  needsOptimizeTokens(currentToolName) {
155
155
  if (currentToolName === 'nexus_optimize_tokens')
156
156
  return false;
157
+ if (currentToolName === 'nexus_mindkit_check')
158
+ return false;
157
159
  if (this.lifecyclePhase === 'pre-bootstrap')
158
160
  return false;
159
161
  if (this.tokenAutoApplied)
@@ -39,6 +39,13 @@ const SETUP_CLIENT_BY_IDE = {
39
39
  cline: 'cline',
40
40
  codex: 'codex',
41
41
  };
42
+ function resolveSetupWorkspaceRoot(explicitWorkspaceRoot) {
43
+ return resolveWorkspaceContext({
44
+ workspaceRoot: explicitWorkspaceRoot,
45
+ cwd: process.cwd(),
46
+ env: process.env,
47
+ }).workspaceRoot;
48
+ }
42
49
  function isSetupMarker(value) {
43
50
  if (!value || typeof value !== 'object') {
44
51
  return false;
@@ -89,7 +96,7 @@ export function writeSetupMarker(marker, markerPath = SETUP_MARKER_PATH) {
89
96
  return nextMarker;
90
97
  }
91
98
  export async function configureIDE(ide, opts = {}) {
92
- const workspaceRoot = resolve(opts.workspaceRoot ?? process.cwd());
99
+ const workspaceRoot = resolveSetupWorkspaceRoot(opts.workspaceRoot);
93
100
  const mappedClient = SETUP_CLIENT_BY_IDE[ide];
94
101
  if (mappedClient) {
95
102
  const definition = getSetupDefinition(mappedClient, {
@@ -285,7 +292,7 @@ export function runArchitectureUpgrade(opts = {}) {
285
292
  }
286
293
  /** Run the install wizard: detect IDEs and write MCP configs. */
287
294
  export async function runInstallWizard(opts = {}) {
288
- const workspaceRoot = resolve(opts.workspaceRoot ?? process.cwd());
295
+ const workspaceRoot = resolveSetupWorkspaceRoot(opts.workspaceRoot);
289
296
  const verbose = opts.verbose ?? true;
290
297
  const dryRun = opts.dryRun ?? false;
291
298
  const setupMarker = readSetupMarker();
@@ -414,6 +421,7 @@ export async function cliSetup(opts = []) {
414
421
  const port = process.env.NEXUS_DASHBOARD_PORT ?? '3377';
415
422
  const dashUrl = `http://localhost:${port}`;
416
423
  const isNew = options.isNewUser ?? false;
424
+ const workspaceRoot = resolveSetupWorkspaceRoot();
417
425
  const ANSI = { cyan: '\x1b[36m', green: '\x1b[32m', yellow: '\x1b[33m', dim: '\x1b[2m', reset: '\x1b[0m' };
418
426
  const isTTY = process.stdout.isTTY === true && !process.env.NO_COLOR;
419
427
  const c = (s, k) => isTTY ? `${ANSI[k]}${s}${ANSI.reset}` : s;
@@ -438,7 +446,7 @@ export async function cliSetup(opts = []) {
438
446
  }
439
447
  catch { /* non-fatal */ }
440
448
  };
441
- const result = await runInstallWizard({ dryRun, verbose: false });
449
+ const result = await runInstallWizard({ workspaceRoot, dryRun, verbose: false });
442
450
  const allIDEs = [...result.configured, ...result.skipped, ...result.errors.map(e => e.ide)];
443
451
  if (allIDEs.length > 0) {
444
452
  for (const ide of allIDEs) {
@@ -499,7 +507,7 @@ export async function cliSetup(opts = []) {
499
507
  let daemonReady = false;
500
508
  if (!dryRun) {
501
509
  try {
502
- const workspace = resolveWorkspaceContext({ workspaceRoot: process.cwd() });
510
+ const workspace = resolveWorkspaceContext({ workspaceRoot, cwd: process.cwd(), env: process.env });
503
511
  await withSpinner('Connecting to daemon', ensureDaemonReady(workspace, { timeoutMs: DEFAULT_DAEMON_READY_TIMEOUT_MS, entrypoint: process.argv[1] }));
504
512
  daemonReady = true;
505
513
  log(` ${c('✓', 'green')} Dashboard: ${c(dashUrl, 'cyan')}`);
package/dist/cli.js CHANGED
@@ -454,11 +454,13 @@ function resolveClaudeDesktopConfigPath() {
454
454
  return join(homedir(), '.claude', 'claude_desktop_config.json');
455
455
  }
456
456
  function getSetupDefinition(clientId) {
457
+ const workspaceRoot = getWorkspaceRoot();
457
458
  const instructionFiles = buildInstructionFiles(clientId);
458
459
  if (clientId === 'codex') {
459
460
  return {
460
461
  id: clientId,
461
462
  label: 'Codex',
463
+ workspaceRoot,
462
464
  configPath: resolveCodexConfigPath(),
463
465
  instructionFiles,
464
466
  };
@@ -467,6 +469,7 @@ function getSetupDefinition(clientId) {
467
469
  return {
468
470
  id: clientId,
469
471
  label: 'Cursor',
472
+ workspaceRoot,
470
473
  configPath: join(homedir(), '.cursor', 'mcp.json'),
471
474
  instructionFiles,
472
475
  };
@@ -475,7 +478,8 @@ function getSetupDefinition(clientId) {
475
478
  return {
476
479
  id: clientId,
477
480
  label: 'Claude Code',
478
- configPath: join(getWorkspaceRoot(), '.mcp.json'),
481
+ workspaceRoot,
482
+ configPath: join(workspaceRoot, '.mcp.json'),
479
483
  instructionFiles,
480
484
  };
481
485
  }
@@ -483,6 +487,7 @@ function getSetupDefinition(clientId) {
483
487
  return {
484
488
  id: clientId,
485
489
  label: 'Claude Desktop',
490
+ workspaceRoot,
486
491
  configPath: resolveClaudeDesktopConfigPath(),
487
492
  instructionFiles,
488
493
  };
@@ -491,6 +496,7 @@ function getSetupDefinition(clientId) {
491
496
  return {
492
497
  id: clientId,
493
498
  label: 'Opencode',
499
+ workspaceRoot,
494
500
  configPath: join(homedir(), '.config', 'opencode', 'opencode.json'),
495
501
  instructionFiles,
496
502
  };
@@ -499,6 +505,7 @@ function getSetupDefinition(clientId) {
499
505
  return {
500
506
  id: clientId,
501
507
  label: 'Windsurf',
508
+ workspaceRoot,
502
509
  configPath: join(homedir(), '.windsurf', 'mcp.json'),
503
510
  instructionFiles,
504
511
  };
@@ -507,6 +514,7 @@ function getSetupDefinition(clientId) {
507
514
  return {
508
515
  id: clientId,
509
516
  label: 'Antigravity / OpenClaw',
517
+ workspaceRoot,
510
518
  configPath: join(homedir(), '.antigravity', 'mcp.json'),
511
519
  instructionFiles,
512
520
  };
@@ -515,6 +523,7 @@ function getSetupDefinition(clientId) {
515
523
  return {
516
524
  id: clientId,
517
525
  label: 'Aider',
526
+ workspaceRoot,
518
527
  configPath: join(homedir(), '.aider', 'mcp.json'),
519
528
  instructionFiles,
520
529
  };
@@ -523,6 +532,7 @@ function getSetupDefinition(clientId) {
523
532
  return {
524
533
  id: clientId,
525
534
  label: 'Continue.dev',
535
+ workspaceRoot,
526
536
  configPath: join(homedir(), '.continue', 'config.json'),
527
537
  instructionFiles,
528
538
  };
@@ -531,6 +541,7 @@ function getSetupDefinition(clientId) {
531
541
  return {
532
542
  id: clientId,
533
543
  label: 'OpenClaw',
544
+ workspaceRoot,
534
545
  configPath: join(homedir(), '.openclaw', 'openclaw.json'),
535
546
  instructionFiles,
536
547
  };
@@ -538,6 +549,7 @@ function getSetupDefinition(clientId) {
538
549
  return {
539
550
  id: clientId,
540
551
  label: 'Cline',
552
+ workspaceRoot,
541
553
  configPath: join(homedir(), '.vscode', 'cline-mcp.json'),
542
554
  instructionFiles,
543
555
  };
@@ -548,10 +560,10 @@ function installSetup(definition) {
548
560
  writeCodexMcpConfig(definition.configPath);
549
561
  }
550
562
  else if (definition.id === 'opencode') {
551
- writeOpencodeConfig(definition.configPath, getWorkspaceRoot());
563
+ writeOpencodeConfig(definition.configPath, definition.workspaceRoot);
552
564
  }
553
565
  else {
554
- writeStandardMcpConfig(definition.configPath, getWorkspaceRoot());
566
+ writeStandardMcpConfig(definition.configPath, definition.workspaceRoot);
555
567
  }
556
568
  }
557
569
  for (const file of definition.instructionFiles) {
@@ -585,12 +597,12 @@ function printSetupPreview(definition) {
585
597
  console.log(JSON.stringify(definition.id === 'opencode'
586
598
  ? {
587
599
  mcp: {
588
- servers: [{ id: 'nexus-prime', ...buildStandardMcpServerConfig(getWorkspaceRoot()) }]
600
+ servers: [{ id: 'nexus-prime', ...buildStandardMcpServerConfig(definition.workspaceRoot) }]
589
601
  }
590
602
  }
591
603
  : {
592
604
  mcpServers: {
593
- 'nexus-prime': buildStandardMcpServerConfig(getWorkspaceRoot())
605
+ 'nexus-prime': buildStandardMcpServerConfig(definition.workspaceRoot)
594
606
  }
595
607
  }, null, 2));
596
608
  }
@@ -612,10 +624,10 @@ function hasExpectedConfig(definition) {
612
624
  return false;
613
625
  if (definition.id === 'opencode') {
614
626
  const server = parsed?.mcp?.['nexus-prime'];
615
- return Boolean(server && isStableNexusMcpServerConfig(server, 'environment'));
627
+ return Boolean(server && isStableNexusMcpServerConfig(server, 'environment', definition.workspaceRoot));
616
628
  }
617
629
  const server = parsed?.mcpServers?.['nexus-prime'];
618
- return Boolean(server && isStableNexusMcpServerConfig(server));
630
+ return Boolean(server && isStableNexusMcpServerConfig(server, 'env', definition.workspaceRoot));
619
631
  }
620
632
  catch {
621
633
  return false;
@@ -1751,7 +1763,7 @@ program
1751
1763
  .description('Manage your Nexus Prime license')
1752
1764
  .addCommand(new Command('activate')
1753
1765
  .description('Activate a license key')
1754
- .argument('<key>', 'License key from nexus-prime.cfd/signup or account page')
1766
+ .argument('<key>', 'License key from the Nexus account page or license team')
1755
1767
  .action((key) => {
1756
1768
  const mgr = getSharedLicenseManager();
1757
1769
  const status = mgr.activate(key);
@@ -1764,7 +1776,8 @@ program
1764
1776
  }
1765
1777
  else {
1766
1778
  console.error(`\u2717 License invalid: ${status.degradedReason ?? 'unknown'}`);
1767
- console.error(' Get a valid key at https://nexus-prime.cfd/signup');
1779
+ console.error(' Request a valid key at https://nexus-prime.cfd/account');
1780
+ console.error(' Or email adarsh@nexus-prime.cfd — licenses are shared within 24 hours.');
1768
1781
  process.exit(1);
1769
1782
  }
1770
1783
  }))
@@ -1792,7 +1805,8 @@ program
1792
1805
  .action(() => {
1793
1806
  getSharedLicenseManager().deactivate();
1794
1807
  console.log('\u2713 License removed. Reverted to Free plan.');
1795
- console.log(' Sign up again at https://nexus-prime.cfd/signup');
1808
+ console.log(' Request a new key at https://nexus-prime.cfd/account');
1809
+ console.log(' Or email adarsh@nexus-prime.cfd — licenses are shared within 24 hours.');
1796
1810
  }))
1797
1811
  .addCommand(new Command('login')
1798
1812
  .description('Login to nexus-prime.cfd for license sync')
@@ -1821,7 +1835,8 @@ program
1821
1835
  }
1822
1836
  catch {
1823
1837
  console.log(' License sync skipped (no license found yet).');
1824
- console.log(' Your free license is generated on signup at nexus-prime.cfd/signup');
1838
+ console.log(' Request a license at https://nexus-prime.cfd/account');
1839
+ console.log(' Or email adarsh@nexus-prime.cfd — licenses are shared within 24 hours.');
1825
1840
  }
1826
1841
  }
1827
1842
  catch (err) {
@@ -1875,12 +1890,13 @@ program
1875
1890
  try {
1876
1891
  const result = await requestUpgrade(plan);
1877
1892
  console.log(`\u2713 Upgrade request submitted (${result.status})`);
1878
- console.log(' We will review your request and notify you when approved.');
1893
+ console.log(' The Nexus Prime team will review it and share licenses within 24 hours.');
1879
1894
  console.log(' Once approved, run: nexus-prime license sync');
1880
1895
  }
1881
1896
  catch (err) {
1882
1897
  console.error(`\u2717 Request failed: ${err instanceof Error ? err.message : String(err)}`);
1883
1898
  console.log(' Visit https://nexus-prime.cfd/account to request an upgrade.');
1899
+ console.log(' Or email adarsh@nexus-prime.cfd with your account email and target plan.');
1884
1900
  process.exit(1);
1885
1901
  }
1886
1902
  }));
@@ -158,12 +158,41 @@ function _renewalNagBanner(status) {
158
158
  </div>`;
159
159
  }
160
160
 
161
+ function _trialActivationBanner(status) {
162
+ if (!status?.trialPhase) return '';
163
+ const motion = status.activationMotion ?? {};
164
+ const required = status.activationRequired === true || status.trialPhase === 'activation' || status.trialPhase === 'expired';
165
+ const days = typeof status.trialDaysRemaining === 'number' ? status.trialDaysRemaining : null;
166
+ const color = status.trialPhase === 'expired' ? 'var(--bad)' : required ? 'var(--warn)' : 'var(--accent)';
167
+ const bg = status.trialPhase === 'expired'
168
+ ? 'oklch(65% 0.22 25 / 8%)'
169
+ : required ? 'oklch(80% 0.16 80 / 8%)' : 'oklch(70% 0.16 250 / 8%)';
170
+ const title = motion.title ?? (required ? 'License required' : '3-day no-license grace');
171
+ const message = motion.message ?? (required
172
+ ? 'The no-license grace period is over. Request a license from the account page or email adarsh@nexus-prime.cfd; the team will share licenses within 24 hours.'
173
+ : 'First 3 days run without a license. Request a license before day 4; the team will share licenses within 24 hours.');
174
+ const remaining = days == null ? '' : `<div style="font-size:11px;color:var(--text-muted);margin-top:4px">${days} day${days === 1 ? '' : 's'} remaining</div>`;
175
+ return `
176
+ <div style="background:${bg};border:1px solid ${color};border-radius:8px;padding:12px 16px;margin-bottom:16px;display:flex;align-items:center;gap:12px;flex-wrap:wrap">
177
+ <div style="flex:1;min-width:220px">
178
+ <div style="font:600 13px ui-sans-serif;color:${color};margin-bottom:3px">${esc(title)}</div>
179
+ <div style="font-size:12px;color:var(--text-muted);line-height:1.45">${esc(message)}</div>
180
+ ${remaining}
181
+ </div>
182
+ <button class="btn btn-primary btn-sm" id="lic-trial-account-btn" style="flex-shrink:0;white-space:nowrap">Request license</button>
183
+ <button class="btn btn-sm" id="lic-trial-activate-btn" style="flex-shrink:0;white-space:nowrap">Activate key</button>
184
+ <button class="btn btn-sm" id="lic-trial-email-btn" style="flex-shrink:0;white-space:nowrap">Email team</button>
185
+ </div>`;
186
+ }
187
+
161
188
  function _renderStep1(tier, status, pcu, formatted) {
162
189
  const plan = PLANS.find(p => p.id === tier) ?? PLANS[0];
163
190
  const valid = status.valid !== false;
164
191
 
165
192
  return `
166
193
  <div class="license-card card">
194
+ ${_trialActivationBanner(status)}
195
+
167
196
  <!-- Renewal nag (shown when expiry < 14 days) -->
168
197
  ${_renewalNagBanner(status)}
169
198
 
@@ -300,6 +329,13 @@ function _attachHandlers() {
300
329
  $('lic-upgrade-btn')?.addEventListener('click', () => { _step = 2; render(); });
301
330
  $('lic-sync-btn')?.addEventListener('click', _doSync);
302
331
  $('lic-deactivate-btn')?.addEventListener('click', _doDeactivate);
332
+ $('lic-trial-activate-btn')?.addEventListener('click', () => { _step = 3; render(); });
333
+ $('lic-trial-account-btn')?.addEventListener('click', () => {
334
+ window.open('https://nexus-prime.cfd/account', '_blank', 'noopener');
335
+ });
336
+ $('lic-trial-email-btn')?.addEventListener('click', () => {
337
+ window.open('mailto:adarsh@nexus-prime.cfd?subject=Nexus%20Prime%20license%20request', '_blank', 'noopener');
338
+ });
303
339
  $('lic-renew-nag-btn')?.addEventListener('click', () => {
304
340
  window.open('https://nexus-prime.cfd/license', '_blank', 'noopener');
305
341
  });
@@ -11,6 +11,7 @@ export interface SetupInstructionFile {
11
11
  export interface SetupDefinition {
12
12
  id: SetupClientId;
13
13
  label: string;
14
+ workspaceRoot: string;
14
15
  configPath?: string;
15
16
  instructionFiles: SetupInstructionFile[];
16
17
  }
@@ -309,8 +309,8 @@ function codexTomlHasAutonomousProfile(doc) {
309
309
  return false;
310
310
  if (codexTomlExposesInternalGraphPeer(doc))
311
311
  return false;
312
- const commandOk = !/(?:^|\n)\s*command\s*=\s*"npx"\s*(?:\n|$)/.test(mcpSection);
313
- const argsOk = /(?:^|\n)\s*args\s*=\s*\[[^\]]*"mcp"[^\]]*\]\s*(?:\n|$)/s.test(mcpSection);
312
+ const commandOk = !/(?:^|\n)\s*command\s*=\s*"(?:npx|nexus-prime)"\s*(?:\n|$)/.test(mcpSection);
313
+ const argsOk = /(?:^|\n)\s*args\s*=\s*\[[^\]]*["'][^"']*cli\.js["'][^\]]*"mcp"[^\]]*\]\s*(?:\n|$)/s.test(mcpSection);
314
314
  const envOk = Boolean(envSection && /(?:^|\n)\s*NEXUS_MCP_TOOL_PROFILE\s*=\s*"autonomous"\s*(?:\n|$)/.test(envSection));
315
315
  const noStartupRewrite = Boolean(envSection && /(?:^|\n)\s*NEXUS_MCP_AUTO_CONFIG\s*=\s*"0"\s*(?:\n|$)/.test(envSection));
316
316
  const fullDnaOnDisconnect = Boolean(envSection && /(?:^|\n)\s*NEXUS_MCP_FULL_DNA_ON_DISCONNECT\s*=\s*"1"\s*(?:\n|$)/.test(envSection));
@@ -548,6 +548,7 @@ export function getSetupDefinition(clientId, options) {
548
548
  return {
549
549
  id: clientId,
550
550
  label: 'Codex',
551
+ workspaceRoot,
551
552
  configPath: resolveCodexConfigPath(),
552
553
  instructionFiles,
553
554
  };
@@ -557,6 +558,7 @@ export function getSetupDefinition(clientId, options) {
557
558
  return {
558
559
  id: clientId,
559
560
  label: 'Cursor',
561
+ workspaceRoot,
560
562
  configPath: join(workspacePath, '.cursor', 'mcp.json'),
561
563
  instructionFiles,
562
564
  };
@@ -565,6 +567,7 @@ export function getSetupDefinition(clientId, options) {
565
567
  return {
566
568
  id: clientId,
567
569
  label: 'Claude Code',
570
+ workspaceRoot,
568
571
  configPath: join(workspaceRoot, '.mcp.json'),
569
572
  instructionFiles,
570
573
  };
@@ -578,6 +581,7 @@ export function getSetupDefinition(clientId, options) {
578
581
  return {
579
582
  id: clientId,
580
583
  label: 'Claude Desktop',
584
+ workspaceRoot,
581
585
  configPath: claudeDesktopPath,
582
586
  instructionFiles,
583
587
  };
@@ -586,6 +590,7 @@ export function getSetupDefinition(clientId, options) {
586
590
  return {
587
591
  id: clientId,
588
592
  label: 'Opencode',
593
+ workspaceRoot,
589
594
  configPath: join(homedir(), '.config', 'opencode', 'opencode.json'),
590
595
  instructionFiles,
591
596
  };
@@ -594,6 +599,7 @@ export function getSetupDefinition(clientId, options) {
594
599
  return {
595
600
  id: clientId,
596
601
  label: 'Windsurf',
602
+ workspaceRoot,
597
603
  configPath: join(homedir(), '.windsurf', 'mcp.json'),
598
604
  instructionFiles,
599
605
  };
@@ -602,6 +608,7 @@ export function getSetupDefinition(clientId, options) {
602
608
  return {
603
609
  id: clientId,
604
610
  label: 'Aider',
611
+ workspaceRoot,
605
612
  configPath: join(homedir(), '.aider', 'mcp.json'),
606
613
  instructionFiles,
607
614
  };
@@ -610,6 +617,7 @@ export function getSetupDefinition(clientId, options) {
610
617
  return {
611
618
  id: clientId,
612
619
  label: 'Continue.dev',
620
+ workspaceRoot,
613
621
  configPath: join(homedir(), '.continue', 'config.json'),
614
622
  instructionFiles,
615
623
  };
@@ -618,6 +626,7 @@ export function getSetupDefinition(clientId, options) {
618
626
  return {
619
627
  id: clientId,
620
628
  label: 'Cline',
629
+ workspaceRoot,
621
630
  configPath: join(homedir(), '.vscode', 'cline-mcp.json'),
622
631
  instructionFiles,
623
632
  };
@@ -626,6 +635,7 @@ export function getSetupDefinition(clientId, options) {
626
635
  return {
627
636
  id: clientId,
628
637
  label: 'OpenClaw',
638
+ workspaceRoot,
629
639
  configPath: join(homedir(), '.openclaw', 'openclaw.json'),
630
640
  instructionFiles,
631
641
  };
@@ -633,6 +643,7 @@ export function getSetupDefinition(clientId, options) {
633
643
  return {
634
644
  id: clientId,
635
645
  label: 'Antigravity',
646
+ workspaceRoot,
636
647
  configPath: join(homedir(), '.antigravity', 'mcp.json'),
637
648
  instructionFiles,
638
649
  };
@@ -697,11 +708,11 @@ export function hasExpectedConfig(definition) {
697
708
  if (definition.id === 'opencode') {
698
709
  const server = parsed?.mcp?.['nexus-prime'];
699
710
  return Boolean(server
700
- && isStableNexusMcpServerConfig(server, 'environment'));
711
+ && isStableNexusMcpServerConfig(server, 'environment', definition.workspaceRoot));
701
712
  }
702
713
  const server = parsed?.mcpServers?.['nexus-prime'];
703
714
  return Boolean(server
704
- && isStableNexusMcpServerConfig(server));
715
+ && isStableNexusMcpServerConfig(server, 'env', definition.workspaceRoot));
705
716
  }
706
717
  catch {
707
718
  return false;
@@ -986,7 +997,7 @@ export function ensureBootstrap(options) {
986
997
  const stat = statSync(configPath);
987
998
  const mtimeMs = stat.mtimeMs;
988
999
  const now = Date.now();
989
- if (now - mtimeMs < 60000) {
1000
+ if (now - mtimeMs < 60000 && hasExpectedConfig(definition)) {
990
1001
  continue;
991
1002
  }
992
1003
  }
@@ -9,4 +9,4 @@ export declare function buildNexusMcpCommand(): {
9
9
  };
10
10
  export declare function buildNexusMcpEnv(workspaceRoot?: string): Record<string, string>;
11
11
  export declare function buildNexusMcpServerConfig(workspaceRoot?: string): NexusMcpServerConfig;
12
- export declare function isStableNexusMcpServerConfig(server: any, envKey?: 'env' | 'environment'): boolean;
12
+ export declare function isStableNexusMcpServerConfig(server: any, envKey?: 'env' | 'environment', expectedWorkspaceRoot?: string): boolean;
@@ -1,4 +1,22 @@
1
+ import { existsSync } from 'fs';
2
+ import { dirname, isAbsolute, resolve } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ function resolveCliEntrypoint() {
5
+ const currentDir = dirname(fileURLToPath(import.meta.url));
6
+ const candidates = [
7
+ resolve(currentDir, '..', 'cli.js'),
8
+ resolve(currentDir, '..', '..', 'dist', 'cli.js'),
9
+ ];
10
+ return candidates.find((candidate) => existsSync(candidate)) ?? null;
11
+ }
1
12
  export function buildNexusMcpCommand() {
13
+ const cliPath = resolveCliEntrypoint();
14
+ if (cliPath) {
15
+ return {
16
+ command: process.execPath,
17
+ args: [cliPath, 'mcp'],
18
+ };
19
+ }
2
20
  return {
3
21
  command: 'nexus-prime',
4
22
  args: ['mcp'],
@@ -26,16 +44,36 @@ export function buildNexusMcpServerConfig(workspaceRoot) {
26
44
  env: buildNexusMcpEnv(workspaceRoot),
27
45
  };
28
46
  }
29
- export function isStableNexusMcpServerConfig(server, envKey = 'env') {
47
+ function sameResolvedPath(a, b) {
48
+ if (typeof a !== 'string' || !a.trim())
49
+ return false;
50
+ try {
51
+ return resolve(a) === resolve(b);
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ }
57
+ export function isStableNexusMcpServerConfig(server, envKey = 'env', expectedWorkspaceRoot) {
30
58
  const env = server?.[envKey];
59
+ const command = typeof server?.command === 'string' ? server.command : '';
31
60
  const args = Array.isArray(server?.args) ? server.args.map((arg) => String(arg)) : [];
61
+ const cliArg = args.find((arg) => /(?:^|[/\\])cli\.js$/.test(arg));
62
+ const workspaceOk = expectedWorkspaceRoot
63
+ ? sameResolvedPath(env?.NEXUS_WORKSPACE_ROOT, expectedWorkspaceRoot)
64
+ : true;
32
65
  return Boolean(server
33
- && typeof server.command === 'string'
34
- && server.command !== 'npx'
66
+ && command
67
+ && command !== 'npx'
68
+ && command !== 'nexus-prime'
69
+ && isAbsolute(command)
70
+ && cliArg
71
+ && isAbsolute(cliArg)
35
72
  && args.includes('mcp')
36
73
  && !args.includes('-y')
37
74
  && env?.NEXUS_MCP_TOOL_PROFILE === 'autonomous'
38
75
  && env?.NEXUS_MCP_AUTO_CONFIG === '0'
39
76
  && env?.NEXUS_DAEMON_AUTOSPAWN === '0'
40
- && env?.NEXUS_MCP_FULL_DNA_ON_DISCONNECT === '1');
77
+ && env?.NEXUS_MCP_FULL_DNA_ON_DISCONNECT === '1'
78
+ && workspaceOk);
41
79
  }
@@ -21,6 +21,12 @@ function getMode() {
21
21
  // Tools that create resources subject to quantity caps
22
22
  const MEMORY_CREATION_TOOLS = new Set(['nexus_store_memory']);
23
23
  const OPERATIVE_CREATION_TOOLS = new Set(['nexus_synapse_hire', 'nexus_synapse_mandate']);
24
+ const LICENSE_SAFE_TOOLS = new Set([
25
+ 'nexus_license_usage',
26
+ 'nexus_runtime_health',
27
+ 'nexus_describe_tool',
28
+ 'nexus_session_dna',
29
+ ]);
24
30
  /**
25
31
  * LicenseEnforcementMiddleware — priority 5 (runs first in the pipeline).
26
32
  *
@@ -35,8 +41,8 @@ export const LicenseEnforcementMiddleware = {
35
41
  const status = lm.getStatus();
36
42
  const mode = getMode();
37
43
  const toolName = ctx.toolName;
38
- // Skip enforcement for license management tools
39
- if (toolName === 'nexus_license_usage')
44
+ // Keep activation/status paths available even when the trial gate is closed.
45
+ if (LICENSE_SAFE_TOOLS.has(toolName))
40
46
  return;
41
47
  // ── No license check ──────────────────────────────────────────────
42
48
  if (status.degradedReason === 'not-activated') {
@@ -46,17 +52,28 @@ export const LicenseEnforcementMiddleware = {
46
52
  // In all modes: let the tool execute (soft gate during ramp)
47
53
  return;
48
54
  }
55
+ if (status.degradedReason === 'activation-required') {
56
+ const msg = trialActiveMessage(status);
57
+ if (mode !== 'audit') {
58
+ ctx.meta.licenseUpgradeHint = msg;
59
+ }
60
+ return;
61
+ }
49
62
  if (status.degradedReason === 'trial-expired') {
63
+ const msg = trialExpiredMessage();
50
64
  if (mode !== 'audit') {
51
- ctx.meta.licenseUpgradeHint = trialExpiredMessage();
65
+ ctx.meta.shortCircuitResult = {
66
+ content: [{ type: 'text', text: msg }],
67
+ };
68
+ ctx.meta.shortCircuitedBy = 'license-enforcement';
52
69
  }
53
- // Fall through to tier check — caps revert to free tier on trial expiry.
70
+ return;
54
71
  }
55
72
  if (status.trial && mode !== 'audit') {
56
73
  // Soft footer so users know the runtime is being kept alive by the
57
74
  // auto-issued local trial. Doesn't gate execution — paid tiers behave
58
75
  // exactly like a real license while the trial is active.
59
- ctx.meta.licenseUpgradeHint = trialActiveMessage(status.expiresAt);
76
+ ctx.meta.licenseUpgradeHint = trialActiveMessage(status);
60
77
  }
61
78
  // ── Tool tier check ───────────────────────────────────────────────
62
79
  if (!isToolAllowed(toolName, status.tier)) {
@@ -6,4 +6,4 @@ export { snapshotPCU, formatPCUStatus, type PCUSnapshot } from './pcu-meter.js';
6
6
  export { capWarningMessage, capExceededMessage, toolGateMessage, noLicenseMessage, trialActiveMessage, trialExpiredMessage, } from './upgrade-prompts.js';
7
7
  export { syncLicense, requestUpgrade } from './license-sync.js';
8
8
  export { loginFromCLI, readAuthToken, readAuthInfo, isLoggedIn, logout } from './web-auth.js';
9
- export type { PlanTier, PlanCaps, LicenseClaims, LicenseStatus, CapType, CapCheckResult, SkillProfile, DarwinScope, } from './types.js';
9
+ export type { PlanTier, TrialPhase, PlanCaps, LicenseClaims, LicenseStatus, CapType, CapCheckResult, SkillProfile, DarwinScope, } from './types.js';
@@ -28,6 +28,8 @@ async function emitLicenseEvent(eventType, payload) {
28
28
  // The matching private key is stored securely on the Nexus Prime licensing backend.
29
29
  const NEXUS_PUBLIC_KEY_B64 = 'MCowBQYDK2VwAyEAbrBiMBqzIyatM/Q/plA0Dn2Y/TAu2UVmWG8guGI0ElQ=';
30
30
  const UPGRADE_URL = 'https://nexus-prime.cfd/pricing';
31
+ const ACCOUNT_URL = 'https://nexus-prime.cfd/account';
32
+ const LICENSE_TEAM_EMAIL = 'adarsh@nexus-prime.cfd';
31
33
  // Warn at this fraction of the cap (e.g. 0.8 = 80%)
32
34
  const WARN_THRESHOLD = 0.8;
33
35
  // Trial configuration. Every install gets a 30-day full-tier trial the first
@@ -35,7 +37,9 @@ const WARN_THRESHOLD = 0.8;
35
37
  // paid tools available — so users can actually evaluate the product. The
36
38
  // trial marker (stateDir/trial.json) records issue + expiry, so subsequent
37
39
  // loads stay deterministic offline. NEXUS_DISABLE_TRIAL=1 opts out (tests).
38
- const TRIAL_DURATION_MS = 30 * 24 * 60 * 60 * 1000;
40
+ const DAY_MS = 24 * 60 * 60 * 1000;
41
+ const TRIAL_DURATION_MS = 30 * DAY_MS;
42
+ const TRIAL_GRACE_MS = 3 * DAY_MS;
39
43
  const TRIAL_TIER = 'enterprise';
40
44
  const TRIAL_MARKER_FILENAME = 'trial.json';
41
45
  function readTrialMarker(markerPath) {
@@ -69,6 +73,64 @@ function writeTrialMarker(markerPath, marker) {
69
73
  // best-effort: trial still applies in memory even if we can't persist it.
70
74
  }
71
75
  }
76
+ function clampTrialMarker(marker, now) {
77
+ const issuedAt = Number.isFinite(marker.issuedAt) && marker.issuedAt <= now + DAY_MS
78
+ ? marker.issuedAt
79
+ : now;
80
+ const maxExpiresAt = issuedAt + TRIAL_DURATION_MS;
81
+ const expiresAt = Number.isFinite(marker.expiresAt) && marker.expiresAt >= issuedAt
82
+ ? Math.min(marker.expiresAt, maxExpiresAt)
83
+ : maxExpiresAt;
84
+ const normalized = {
85
+ issuedAt,
86
+ expiresAt,
87
+ tier: TRIAL_TIER,
88
+ orgId: marker.orgId ?? null,
89
+ };
90
+ return {
91
+ marker: normalized,
92
+ changed: normalized.issuedAt !== marker.issuedAt
93
+ || normalized.expiresAt !== marker.expiresAt
94
+ || normalized.tier !== marker.tier
95
+ || normalized.orgId !== marker.orgId,
96
+ };
97
+ }
98
+ function daysUntil(timestamp, now) {
99
+ return Math.max(0, Math.ceil((timestamp - now) / DAY_MS));
100
+ }
101
+ function buildActivationMotion(phase, daysRemaining) {
102
+ if (phase === 'expired') {
103
+ return {
104
+ phase: 'expired',
105
+ required: true,
106
+ title: 'Trial expired',
107
+ message: `The trial window is over. Request a license in the Nexus account page or email ${LICENSE_TEAM_EMAIL}. The team will share licenses within 24 hours.`,
108
+ action: 'Request license',
109
+ url: ACCOUNT_URL,
110
+ command: 'nexus-prime license activate <token>',
111
+ };
112
+ }
113
+ if (phase === 'activation') {
114
+ return {
115
+ phase: 'activation',
116
+ required: false,
117
+ title: 'Activate license',
118
+ message: `The 3-day no-license grace period is over, but the 30-day trial remains active for ${daysRemaining} day${daysRemaining === 1 ? '' : 's'}. Keep running Nexus Prime and ask the user to request or activate a license soon. Request one in the Nexus account page or email ${LICENSE_TEAM_EMAIL}; the team will share licenses within 24 hours.`,
119
+ action: 'Request license',
120
+ url: ACCOUNT_URL,
121
+ command: 'nexus-prime license activate <token>',
122
+ };
123
+ }
124
+ return {
125
+ phase: 'grace',
126
+ required: false,
127
+ title: '3-day no-license grace',
128
+ message: `Nexus Prime is in the first 3-day no-license grace period. No license is mandatory during these first 3 days; ask the user to request a license soon so approvals can be shared within 24 hours by the Nexus Prime team.`,
129
+ action: 'Request license',
130
+ url: ACCOUNT_URL,
131
+ command: 'nexus-prime license activate <token>',
132
+ };
133
+ }
72
134
  // ── Helpers ──────────────────────────────────────────────────────────────────
73
135
  function base64urlDecode(s) {
74
136
  return Buffer.from(s.replace(/-/g, '+').replace(/_/g, '/'), 'base64');
@@ -101,7 +163,11 @@ function buildFreeStatus(degradedReason) {
101
163
  degradedReason,
102
164
  };
103
165
  }
104
- function buildTrialStatus(marker) {
166
+ function buildTrialStatus(marker, now = Date.now()) {
167
+ const graceEndsAt = marker.issuedAt + TRIAL_GRACE_MS;
168
+ const daysRemaining = daysUntil(marker.expiresAt, now);
169
+ const trialPhase = now >= graceEndsAt ? 'activation' : 'grace';
170
+ const trialDaysElapsed = Math.max(0, Math.floor((now - marker.issuedAt) / DAY_MS));
105
171
  return {
106
172
  valid: true,
107
173
  tier: marker.tier,
@@ -109,6 +175,26 @@ function buildTrialStatus(marker) {
109
175
  expiresAt: marker.expiresAt,
110
176
  orgId: marker.orgId,
111
177
  trial: true,
178
+ trialPhase,
179
+ trialIssuedAt: marker.issuedAt,
180
+ trialGraceEndsAt: graceEndsAt,
181
+ trialDaysElapsed,
182
+ trialDaysRemaining: daysRemaining,
183
+ activationRequired: false,
184
+ activationMotion: buildActivationMotion(trialPhase, daysRemaining),
185
+ };
186
+ }
187
+ function buildTrialExpiredStatus(marker, now = Date.now()) {
188
+ return {
189
+ ...buildFreeStatus('trial-expired'),
190
+ trial: false,
191
+ trialPhase: 'expired',
192
+ trialIssuedAt: marker.issuedAt,
193
+ trialGraceEndsAt: marker.issuedAt + TRIAL_GRACE_MS,
194
+ trialDaysElapsed: Math.max(0, Math.floor((now - marker.issuedAt) / DAY_MS)),
195
+ trialDaysRemaining: 0,
196
+ activationRequired: true,
197
+ activationMotion: buildActivationMotion('expired', 0),
112
198
  };
113
199
  }
114
200
  function validateClaims(claims) {
@@ -240,10 +326,17 @@ export class LicenseManager {
240
326
  };
241
327
  writeTrialMarker(this.trialPath, marker);
242
328
  }
329
+ else {
330
+ const normalized = clampTrialMarker(marker, now);
331
+ marker = normalized.marker;
332
+ if (normalized.changed) {
333
+ writeTrialMarker(this.trialPath, marker);
334
+ }
335
+ }
243
336
  if (marker.expiresAt <= now) {
244
- return buildFreeStatus('trial-expired');
337
+ return buildTrialExpiredStatus(marker, now);
245
338
  }
246
- return buildTrialStatus(marker);
339
+ return buildTrialStatus(marker, now);
247
340
  }
248
341
  /** Returns the path of the trial marker (for tests + status surfaces). */
249
342
  getTrialMarkerPath() {
@@ -22,7 +22,8 @@ export async function syncLicense() {
22
22
  const authToken = readAuthToken();
23
23
  if (!authToken) {
24
24
  throw new Error('Not logged in. Run: nexus-prime license login\n' +
25
- 'Or activate manually: nexus-prime license activate <token>');
25
+ 'Or request a license: https://nexus-prime.cfd/account\n' +
26
+ 'Email fallback: adarsh@nexus-prime.cfd');
26
27
  }
27
28
  const res = await fetch(`${API_BASE}/api/license/current`, {
28
29
  headers: { Authorization: `Bearer ${authToken}` },
@@ -37,7 +38,7 @@ export async function syncLicense() {
37
38
  }
38
39
  const data = await res.json();
39
40
  if (!data.signed_key) {
40
- throw new Error('No license key found. Sign up at https://nexus-prime.cfd/signup');
41
+ throw new Error('No license key found. Request one at https://nexus-prime.cfd/account or email adarsh@nexus-prime.cfd. Licenses are reviewed within 24 hours.');
41
42
  }
42
43
  const lm = getSharedLicenseManager();
43
44
  return lm.activate(data.signed_key);
@@ -49,7 +50,7 @@ export async function syncLicense() {
49
50
  export async function requestUpgrade(requestedPlan) {
50
51
  const authToken = readAuthToken();
51
52
  if (!authToken) {
52
- throw new Error('Not logged in. Run: nexus-prime license login');
53
+ throw new Error('Not logged in. Run: nexus-prime license login, or use https://nexus-prime.cfd/account / adarsh@nexus-prime.cfd.');
53
54
  }
54
55
  const res = await fetch(`${API_BASE}/api/license/request-upgrade`, {
55
56
  method: 'POST',
@@ -1,6 +1,7 @@
1
1
  export type PlanTier = 'free' | 'pro' | 'team' | 'enterprise';
2
2
  export type SkillProfile = 'foundation' | 'all-builtin' | 'all-custom' | 'all-registry';
3
3
  export type DarwinScope = 'off' | 'solo' | 'team' | 'fleet';
4
+ export type TrialPhase = 'grace' | 'activation' | 'expired';
4
5
  export interface PlanCaps {
5
6
  maxProjects: number;
6
7
  maxMemoryEntries: number;
@@ -27,9 +28,24 @@ export interface LicenseStatus {
27
28
  orgId: string | null;
28
29
  /** True when the active status is the auto-issued local trial. */
29
30
  trial?: boolean;
30
- /** Reason a license is degraded; absent when status is valid. Trials report
31
- * `trial-expired` after the auto-issued window has closed. */
32
- degradedReason?: 'expired' | 'signature-invalid' | 'malformed' | 'not-activated' | 'trial-expired';
31
+ /** Trial phase for PLG surfaces. First three days are quiet grace; the rest of the trial uses soft activation prompts. */
32
+ trialPhase?: TrialPhase;
33
+ trialIssuedAt?: number;
34
+ trialGraceEndsAt?: number;
35
+ trialDaysElapsed?: number;
36
+ trialDaysRemaining?: number;
37
+ activationRequired?: boolean;
38
+ activationMotion?: {
39
+ phase: TrialPhase;
40
+ required: boolean;
41
+ title: string;
42
+ message: string;
43
+ action: string;
44
+ url: string;
45
+ command: string;
46
+ };
47
+ /** Reason a license is degraded; absent when status is valid. */
48
+ degradedReason?: 'expired' | 'signature-invalid' | 'malformed' | 'not-activated' | 'activation-required' | 'trial-expired';
33
49
  }
34
50
  export type CapType = 'memory_entries' | 'projects' | 'operatives';
35
51
  export interface CapCheckResult {
@@ -1,4 +1,4 @@
1
- import type { PlanTier, CapCheckResult } from './types.js';
1
+ import type { PlanTier, CapCheckResult, LicenseStatus } from './types.js';
2
2
  /**
3
3
  * Soft nudge at 80% of cap — single line appended to tool response.
4
4
  */
@@ -19,7 +19,7 @@ export declare function noLicenseMessage(): string;
19
19
  * Trial is active. Soft hint that surfaces alongside paid tools so users
20
20
  * know the auto-issued window is what's keeping their runtime alive.
21
21
  */
22
- export declare function trialActiveMessage(expiresAt: number | null): string;
22
+ export declare function trialActiveMessage(statusOrExpiresAt: LicenseStatus | number | null): string;
23
23
  /**
24
24
  * Trial has expired — runtime drops to free caps. Tell the user clearly so
25
25
  * the gap between trial-expired and a real license is obvious.
@@ -14,6 +14,7 @@ async function emitUpgradeNudge(currentTier, feature, message, ctaUrl) {
14
14
  }
15
15
  const UPGRADE_URL = 'https://nexus-prime.cfd/account';
16
16
  const SIGNUP_URL = 'https://nexus-prime.cfd/signup';
17
+ const LICENSE_TEAM_EMAIL = 'adarsh@nexus-prime.cfd';
17
18
  const CAP_LABELS = {
18
19
  memory_entries: 'memory entries',
19
20
  projects: 'projects',
@@ -55,21 +56,48 @@ export function toolGateMessage(toolName, requiredTier, currentTier) {
55
56
  */
56
57
  export function noLicenseMessage() {
57
58
  return [
58
- `[Nexus Prime] No license activated. Sign up for your free license:`,
59
+ `[Nexus Prime] No license activated. Request a license before paid-tier work runs:`,
59
60
  ` → ${SIGNUP_URL}`,
60
- ` Then: nexus-prime license activate <your-token>`,
61
+ ` Account: ${UPGRADE_URL}`,
62
+ ` Email: ${LICENSE_TEAM_EMAIL}`,
63
+ ` Licenses are reviewed and shared by the team within 24 hours.`,
64
+ ` Then activate: nexus-prime license activate <your-token>`,
61
65
  ].join('\n');
62
66
  }
63
67
  /**
64
68
  * Trial is active. Soft hint that surfaces alongside paid tools so users
65
69
  * know the auto-issued window is what's keeping their runtime alive.
66
70
  */
67
- export function trialActiveMessage(expiresAt) {
68
- const daysLeft = expiresAt ? Math.max(0, Math.ceil((expiresAt - Date.now()) / (24 * 60 * 60 * 1000))) : 0;
71
+ export function trialActiveMessage(statusOrExpiresAt) {
72
+ const status = typeof statusOrExpiresAt === 'object' ? statusOrExpiresAt : null;
73
+ const expiresAt = status ? status.expiresAt : typeof statusOrExpiresAt === 'number' ? statusOrExpiresAt : null;
74
+ const daysLeft = status?.trialDaysRemaining
75
+ ?? (expiresAt ? Math.max(0, Math.ceil((expiresAt - Date.now()) / (24 * 60 * 60 * 1000))) : 0);
76
+ if (status?.activationRequired) {
77
+ return [
78
+ `[Nexus Prime] License activation requested.`,
79
+ ` Agent motion: ask the user to request or activate a Nexus Prime license now, but do not stop free/trial-safe work unless the trial has expired.`,
80
+ ` Trial window: ${daysLeft} day${daysLeft === 1 ? '' : 's'} remaining.`,
81
+ ` Request: ${UPGRADE_URL}`,
82
+ ` Email: ${LICENSE_TEAM_EMAIL}`,
83
+ ` The Nexus Prime team will share licenses within 24 hours.`,
84
+ ` Activate: nexus-prime license activate <token>`,
85
+ ].join('\n');
86
+ }
87
+ if (status?.trialPhase === 'activation') {
88
+ return [
89
+ `[Nexus Prime] Trial active (${daysLeft} day${daysLeft === 1 ? '' : 's'} remaining).`,
90
+ ` Agent motion: the first 3 no-license days are over; ask the user to request or activate a license soon, then keep working.`,
91
+ ` Request: ${UPGRADE_URL}`,
92
+ ` Email: ${LICENSE_TEAM_EMAIL}`,
93
+ ` Licenses are reviewed and shared within 24 hours.`,
94
+ ].join('\n');
95
+ }
69
96
  return [
70
- `[Nexus Prime] Trial active (${daysLeft} day${daysLeft === 1 ? '' : 's'} remaining).`,
71
- ` Activate a license: nexus-prime license activate <token>`,
72
- ` Sign up: ${SIGNUP_URL}`,
97
+ `[Nexus Prime] 3-day no-license grace active (${daysLeft} day${daysLeft === 1 ? '' : 's'} in the trial window).`,
98
+ ` No license is mandatory during the first 3 days. Request one soon: ${UPGRADE_URL}`,
99
+ ` Email fallback: ${LICENSE_TEAM_EMAIL}`,
100
+ ` Licenses are reviewed and shared within 24 hours.`,
73
101
  ].join('\n');
74
102
  }
75
103
  /**
@@ -78,8 +106,10 @@ export function trialActiveMessage(expiresAt) {
78
106
  */
79
107
  export function trialExpiredMessage() {
80
108
  return [
81
- `[Nexus Prime] Trial expired — running on free caps.`,
109
+ `[Nexus Prime] Trial expired — license required for paid-tier work.`,
110
+ ` Request: ${UPGRADE_URL}`,
111
+ ` Email: ${LICENSE_TEAM_EMAIL}`,
112
+ ` Licenses are reviewed and shared within 24 hours.`,
82
113
  ` Activate: nexus-prime license activate <token>`,
83
- ` Pricing: ${UPGRADE_URL}`,
84
114
  ].join('\n');
85
115
  }
@@ -7,8 +7,8 @@ interface AuthTokens {
7
7
  /**
8
8
  * Login to nexus-prime.cfd and store auth tokens locally. Errors are
9
9
  * normalised so signup/signin failures don't surface raw HTTP codes —
10
- * users get something actionable, plus a reminder that the local 30-day
11
- * trial keeps the runtime working while they sort out account state.
10
+ * users get something actionable, plus a reminder that the local no-license
11
+ * grace period is intentionally short and licenses are reviewed manually.
12
12
  */
13
13
  export declare function loginFromCLI(email: string, password: string): Promise<{
14
14
  email: string;
@@ -12,8 +12,8 @@ const API_BASE = process.env.NEXUS_WEB_API_URL ?? 'https://nexus-prime.cfd';
12
12
  /**
13
13
  * Login to nexus-prime.cfd and store auth tokens locally. Errors are
14
14
  * normalised so signup/signin failures don't surface raw HTTP codes —
15
- * users get something actionable, plus a reminder that the local 30-day
16
- * trial keeps the runtime working while they sort out account state.
15
+ * users get something actionable, plus a reminder that the local no-license
16
+ * grace period is intentionally short and licenses are reviewed manually.
17
17
  */
18
18
  export async function loginFromCLI(email, password) {
19
19
  let res;
@@ -28,8 +28,8 @@ export async function loginFromCLI(email, password) {
28
28
  catch (err) {
29
29
  const reason = err instanceof Error ? err.message : String(err);
30
30
  throw new Error(`Could not reach ${API_BASE}: ${reason}. `
31
- + `Your 30-day local trial remains active run \`nexus-prime status\` to verify, `
32
- + `then retry login when the network is back.`);
31
+ + `If your 3-day no-license grace is over, request a license at ${API_BASE}/account `
32
+ + `or email adarsh@nexus-prime.cfd. Licenses are shared within 24 hours.`);
33
33
  }
34
34
  if (!res.ok) {
35
35
  const body = await res.json().catch(() => ({}));
@@ -41,10 +41,10 @@ export async function loginFromCLI(email, password) {
41
41
  }
42
42
  if (res.status === 404) {
43
43
  throw new Error(`No account for ${email}. Sign up at ${API_BASE}/signup, then retry. `
44
- + `(Your 30-day local trial keeps the runtime active in the meantime.)`);
44
+ + `If you need manual help, email adarsh@nexus-prime.cfd. Licenses are shared within 24 hours.`);
45
45
  }
46
46
  if (res.status >= 500) {
47
- throw new Error(`Server error (${res.status}) at ${API_BASE}. Your 30-day local trial keeps the runtime active try again shortly.`);
47
+ throw new Error(`Server error (${res.status}) at ${API_BASE}. Try again shortly, or email adarsh@nexus-prime.cfd for a manual license review within 24 hours.`);
48
48
  }
49
49
  throw new Error(detail ?? `Login failed (HTTP ${res.status})`);
50
50
  }
@@ -122,17 +122,19 @@ async function runWithRetry(maxRetries = 3, delayMs = 1000) {
122
122
  registerInstall(installId || 'unknown', version);
123
123
  }
124
124
  catch { /* non-fatal — install tracking is best-effort */ }
125
- // Show license signup prompt
125
+ // Show license request prompt
126
126
  if (shouldShowInstallBanner()) {
127
127
  try {
128
128
  const licenseKeyPath = path.join(os.homedir(), '.nexus-prime', 'license.key');
129
129
  if (!fs.existsSync(licenseKeyPath)) {
130
130
  console.log('');
131
131
  console.log('\u2554' + '\u2550'.repeat(50) + '\u2557');
132
- console.log('\u2551 Sign up for your free license: \u2551');
133
- console.log('\u2551 \u2192 https://nexus-prime.cfd/signup \u2551');
132
+ console.log('\u2551 3-day no-license grace is active. \u2551');
133
+ console.log('\u2551 Request a license before day 4: \u2551');
134
+ console.log('\u2551 \u2192 https://nexus-prime.cfd/account \u2551');
135
+ console.log('\u2551 \u2192 adarsh@nexus-prime.cfd \u2551');
134
136
  console.log('\u2551 \u2551');
135
- console.log('\u2551 Then activate: \u2551');
137
+ console.log('\u2551 Team shares licenses within 24 hours. \u2551');
136
138
  console.log('\u2551 nexus-prime license activate <your-token> \u2551');
137
139
  console.log('\u255A' + '\u2550'.repeat(50) + '\u255D');
138
140
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexus-prime",
3
- "version": "7.9.15",
3
+ "version": "7.9.17",
4
4
  "description": "Local-first MCP control plane for coding agents with bootstrap-orchestrate execution, memory fabric, token budgeting, and worktree-backed swarms",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",