@neus/sdk 1.1.6 → 1.2.0

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/cli/neus.mjs CHANGED
@@ -10,6 +10,13 @@ import {
10
10
  NEUS_MCP_URL,
11
11
  buildNeusMcpHttpConfig
12
12
  } from '../mcp-hosts.js';
13
+ import {
14
+ resolveRuntimeBundleFromMcp,
15
+ RUNTIME_MOUNT_SCHEMA,
16
+ normalizeWallet,
17
+ evaluateMountFileHealth
18
+ } from '../runtime-mount.js';
19
+ import { applyRuntimeBundle, readMountManifest } from '../runtime-adapters.js';
13
20
 
14
21
  const __cliDir = path.dirname(fileURLToPath(import.meta.url));
15
22
  const CLI_PACKAGE_VERSION = (() => {
@@ -590,7 +597,10 @@ function parseArgs(argv) {
590
597
  json: false,
591
598
  dryRun: false,
592
599
  project: false,
593
- oauth: false
600
+ oauth: false,
601
+ agent: '',
602
+ apply: '',
603
+ agentTarget: ''
594
604
  };
595
605
 
596
606
  for (let index = 1; index < argv.length; index += 1) {
@@ -650,6 +660,24 @@ function parseArgs(argv) {
650
660
  options.oauth = true;
651
661
  continue;
652
662
  }
663
+ if (token === '--agent') {
664
+ const value = argv[index + 1];
665
+ if (!value) throw new Error('--agent requires a value');
666
+ options.agent = value.trim();
667
+ index += 1;
668
+ continue;
669
+ }
670
+ if (token === '--apply') {
671
+ const value = argv[index + 1];
672
+ if (!value) throw new Error('--apply requires a value (cursor, claude, or codex)');
673
+ options.apply = value.trim().toLowerCase();
674
+ index += 1;
675
+ continue;
676
+ }
677
+ if (command === 'mount' && !token.startsWith('-') && !options.agentTarget) {
678
+ options.agentTarget = token;
679
+ continue;
680
+ }
653
681
  if (token === '--help' || token === '-h') {
654
682
  return { command: 'help', options };
655
683
  }
@@ -675,6 +703,7 @@ function printUsage(exitCode = 0) {
675
703
  ' check Confirm setup and live NEUS connection (alias for doctor --live)',
676
704
  ' examples Show assistant prompts to try after install',
677
705
  ' doctor Deep check: config status, profile connection, and live MCP context',
706
+ ' mount Mount proof-backed agent context for any runtime',
678
707
  ' import Detect and package supported assistant context for NEUS portability',
679
708
  ' export Export the latest local NEUS portable agent manifest',
680
709
  ' help Show this message',
@@ -688,6 +717,8 @@ function printUsage(exitCode = 0) {
688
717
  ' --to <format> Export format: manifest or json',
689
718
  ' --output <path> Write exported manifest to a specific path',
690
719
  ' --live Run live MCP checks (uses IDE credential or --access-key)',
720
+ ' --agent <agentId> Agent id for mount (also: neus mount <agentId>)',
721
+ ' --apply <cursor|claude|codex> Write mounted agent rules to the current project',
691
722
  ' --json Print JSON output',
692
723
  ' --dry-run Preview changes without writing files'
693
724
  ];
@@ -1378,6 +1409,151 @@ async function callMcpTool({ name, args, accessKey, sessionId, signal }) {
1378
1409
  };
1379
1410
  }
1380
1411
 
1412
+ async function initializeMcpSession(accessKey, signal) {
1413
+ const init = await postMcpJsonRpc({
1414
+ id: 1,
1415
+ method: 'initialize',
1416
+ params: {
1417
+ protocolVersion: '2025-11-25',
1418
+ capabilities: {},
1419
+ clientInfo: { name: 'neus-cli', version: CLI_PACKAGE_VERSION }
1420
+ },
1421
+ accessKey,
1422
+ signal
1423
+ });
1424
+ if (!init.response.ok || init.json?.error) {
1425
+ throw new Error(init.json?.error?.message || 'MCP initialize failed');
1426
+ }
1427
+ return { sessionId: init.sessionId || '' };
1428
+ }
1429
+
1430
+ async function evaluateAgentMountDoctor(accessKey, cwd, signal) {
1431
+ const manifest = readMountManifest(cwd);
1432
+ const fileHealth = evaluateMountFileHealth(manifest);
1433
+ const out = {
1434
+ mountFilePresent: Boolean(manifest),
1435
+ mountFileValid: fileHealth.mountFileValid,
1436
+ mountNeedsRefresh: fileHealth.needsRefresh,
1437
+ mountRefreshReason: fileHealth.reason,
1438
+ missingDelegation: fileHealth.missingDelegation,
1439
+ delegationExpired: fileHealth.delegationExpired,
1440
+ mountAgentId: manifest?.identity?.agentId || null,
1441
+ agentVerified: false,
1442
+ agentLinkStatus: null
1443
+ };
1444
+ if (!accessKey) return out;
1445
+
1446
+ let sessionId = '';
1447
+ try {
1448
+ const init = await initializeMcpSession(accessKey, signal);
1449
+ sessionId = init.sessionId;
1450
+ } catch {
1451
+ return out;
1452
+ }
1453
+
1454
+ const agentId = out.mountAgentId || manifest?.identity?.agentId;
1455
+ const agentWallet = manifest?.identity?.agentWallet;
1456
+ if (agentWallet) {
1457
+ const link = await callMcpTool({
1458
+ name: 'neus_agent_link',
1459
+ args: { agentWallet },
1460
+ accessKey,
1461
+ sessionId,
1462
+ signal
1463
+ });
1464
+ if (link.ok) {
1465
+ out.agentLinkStatus = link.payload?.status || (link.payload?.linked ? 'ok' : 'link_required');
1466
+ out.agentVerified = Boolean(link.payload?.linked);
1467
+ }
1468
+ } else if (agentId) {
1469
+ try {
1470
+ const bundle = await resolveRuntimeBundleFromMcp({
1471
+ callMcpTool: args => callMcpTool({ ...args, accessKey, sessionId, signal }),
1472
+ accessKey,
1473
+ agentId,
1474
+ signal
1475
+ });
1476
+ out.agentVerified = Boolean(bundle?.trust?.identityQHash && bundle?.delegation);
1477
+ out.mountAgentId = bundle.identity?.agentId || agentId;
1478
+ } catch {
1479
+ out.agentVerified = false;
1480
+ }
1481
+ }
1482
+ return out;
1483
+ }
1484
+
1485
+ async function runMount(options) {
1486
+ const cwd = process.cwd();
1487
+ const scope = resolveScope(options);
1488
+ const accessKey = resolveLiveAccessKey(options, scope, cwd);
1489
+ const agentTarget = String(options.agentTarget || options.agent || '').trim();
1490
+ if (!agentTarget) {
1491
+ throw new Error('Usage: neus mount <agentId> [--apply cursor|claude|codex]');
1492
+ }
1493
+ if (!accessKey) {
1494
+ throw new Error('Credential required. Run `neus auth` or pass --access-key.');
1495
+ }
1496
+
1497
+ const controller = new AbortController();
1498
+ const timeout = setTimeout(() => controller.abort(), 30000);
1499
+ try {
1500
+ const bundle = await resolveRuntimeBundleFromMcp({
1501
+ callMcpTool: args => callMcpTool({ ...args, accessKey, signal: controller.signal }),
1502
+ initializeMcp: () => initializeMcpSession(accessKey, controller.signal),
1503
+ accessKey,
1504
+ agentId: agentTarget,
1505
+ signal: controller.signal
1506
+ });
1507
+
1508
+ const applyFlavor = String(options.apply || '').trim().toLowerCase();
1509
+ let applyResult = null;
1510
+ if (applyFlavor) {
1511
+ if (!['cursor', 'claude', 'codex'].includes(applyFlavor)) {
1512
+ throw new Error('--apply must be cursor, claude, or codex');
1513
+ }
1514
+ applyResult = applyRuntimeBundle(applyFlavor, bundle, cwd, { dryRun: options.dryRun });
1515
+ } else if (!options.json) {
1516
+ applyRuntimeBundle('cursor', bundle, cwd, { dryRun: options.dryRun });
1517
+ }
1518
+
1519
+ const payload = {
1520
+ command: 'mount',
1521
+ schema: RUNTIME_MOUNT_SCHEMA,
1522
+ agentId: bundle.identity.agentId,
1523
+ bundle,
1524
+ applied: applyResult,
1525
+ dryRun: Boolean(options.dryRun)
1526
+ };
1527
+
1528
+ if (options.json) {
1529
+ printJson(payload);
1530
+ return payload;
1531
+ }
1532
+
1533
+ emitCliBanner(options);
1534
+ writeCliLine(paint('mount', 'green'));
1535
+ logStep('ok', 'agent', bundle.identity.agentLabel || bundle.identity.agentId);
1536
+ writeGuidanceLine(`Identity receipt: ${bundle.trust.identityProofUrl}`);
1537
+ if (bundle.trust.delegationProofUrl) {
1538
+ writeGuidanceLine(`Delegation receipt: ${bundle.trust.delegationProofUrl}`);
1539
+ } else {
1540
+ writeGuidanceLine('Delegation not on file — run agent setup on neus.network before scoped actions.');
1541
+ }
1542
+ if (applyResult) {
1543
+ for (const filePath of applyResult.written) {
1544
+ logStep('ok', 'wrote', filePath);
1545
+ }
1546
+ } else if (!options.dryRun) {
1547
+ logStep('ok', 'wrote', path.join(cwd, '.neus', 'mount.json'));
1548
+ }
1549
+ writeGuidanceLine('Start a new Agent chat so mounted rules load. Use NEUS Verify before sensitive actions.');
1550
+ writeCliLine('');
1551
+ return payload;
1552
+ } finally {
1553
+ clearTimeout(timeout);
1554
+ }
1555
+ }
1556
+
1381
1557
  async function runLiveMcpDiagnostics(accessKey) {
1382
1558
  if (!accessKey) {
1383
1559
  return {
@@ -1603,96 +1779,108 @@ async function runAuthBrowser(options) {
1603
1779
  const codeChallenge = deriveCodeChallenge(codeVerifier);
1604
1780
 
1605
1781
  return new Promise((resolve, reject) => {
1782
+ let settled = false;
1783
+ function finish(error, value) {
1784
+ if (settled) return;
1785
+ settled = true;
1786
+ server.close();
1787
+ if (error) reject(error);
1788
+ else resolve(value);
1789
+ }
1790
+
1606
1791
  const server = createServer((req, res) => {
1607
1792
  const url = new URL(req.url, `http://127.0.0.1:${server.address().port}`);
1608
- if (url.pathname === '/callback') {
1609
- const returnedState = url.searchParams.get('state');
1610
- if (!returnedState || returnedState !== csrfState) {
1611
- res.writeHead(403, { 'Content-Type': 'text/html' });
1612
- res.end('<html><body><h2>Security check failed</h2><p>Invalid request. Try again.</p></body></html>');
1613
- server.close();
1614
- reject(new Error('CSRF state mismatch'));
1615
- return;
1616
- }
1617
1793
 
1618
- const code = url.searchParams.get('code');
1619
- const error = url.searchParams.get('error');
1794
+ // Ignore browser noise; keep the server alive for the real callback.
1795
+ if (url.pathname === '/favicon.ico') {
1796
+ res.writeHead(204);
1797
+ res.end();
1798
+ return;
1799
+ }
1620
1800
 
1621
- if (error) {
1622
- res.writeHead(200, { 'Content-Type': 'text/html' });
1623
- res.end('<html><body><h2>Authentication failed</h2><p>You can close this tab and try again.</p></body></html>');
1624
- server.close();
1625
- reject(new Error(`Authentication failed: ${error}`));
1626
- return;
1627
- }
1801
+ if (url.pathname !== '/callback') {
1802
+ res.writeHead(404);
1803
+ res.end();
1804
+ return;
1805
+ }
1628
1806
 
1629
- if (!code) {
1630
- res.writeHead(200, { 'Content-Type': 'text/html' });
1631
- res.end('<html><body><h2>Missing auth code</h2><p>You can close this tab and try again.</p></body></html>');
1632
- server.close();
1633
- reject(new Error('No auth code received from callback'));
1634
- return;
1635
- }
1807
+ const returnedState = url.searchParams.get('state');
1808
+ if (!returnedState || returnedState !== csrfState) {
1809
+ res.writeHead(403, { 'Content-Type': 'text/html' });
1810
+ res.end('<html><body><h2>Security check failed</h2><p>Invalid request. Try again.</p></body></html>');
1811
+ finish(new Error('CSRF state mismatch'));
1812
+ return;
1813
+ }
1636
1814
 
1637
- const redirectUri = `http://127.0.0.1:${server.address().port}/callback`;
1638
- const params = new URLSearchParams();
1639
- params.set('grant_type', 'authorization_code');
1640
- params.set('code', code);
1641
- params.set('redirect_uri', redirectUri);
1642
- params.set('client_id', NEUS_OAUTH_CLIENT_ID);
1643
- params.set('code_verifier', codeVerifier);
1644
- params.set('resource', NEUS_MCP_RESOURCE);
1815
+ const code = url.searchParams.get('code');
1816
+ const error = url.searchParams.get('error');
1645
1817
 
1646
- fetch(NEUS_TOKEN_ENDPOINT, {
1647
- method: 'POST',
1648
- headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
1649
- body: params.toString(),
1650
- signal: AbortSignal.timeout(15_000),
1651
- })
1652
- .then(tokenResp => tokenResp.json())
1653
- .then(tokenJson => {
1654
- if (!tokenJson.access_token) {
1655
- res.writeHead(200, { 'Content-Type': 'text/html' });
1656
- res.end('<html><body><h2>Token exchange failed</h2><p>Please try again.</p></body></html>');
1657
- server.close();
1658
- reject(new Error(tokenJson.error_description || tokenJson.error || 'Token exchange failed'));
1659
- return;
1660
- }
1661
-
1662
- const accessToken = tokenJson.access_token;
1663
- res.writeHead(200, { 'Content-Type': 'text/html' });
1664
- res.end('<html><body><h2>Authenticated</h2><p>You can close this tab and return to your terminal.</p></body></html>');
1665
- server.close();
1666
-
1667
- const results = runClientOperations(browserManagedClients, scope, cwd, options.dryRun, client =>
1668
- installClient(client, scope, accessToken, options.dryRun, cwd)
1669
- );
1670
- results.push(
1671
- ...runClientOperations(hostManagedClients, scope, cwd, options.dryRun, () =>
1672
- authCodex(scope, options.dryRun, cwd, options)
1673
- )
1674
- );
1675
- const payload = {
1676
- command: 'auth',
1677
- scope,
1678
- clients,
1679
- accessKeyConfigured: true,
1680
- authMethod: 'browser',
1681
- results,
1682
- hasErrors: results.some(result => result.error)
1683
- };
1684
- resolve(payload);
1685
- })
1686
- .catch(err => {
1687
- res.writeHead(200, { 'Content-Type': 'text/html' });
1688
- res.end('<html><body><h2>Connection error</h2><p>Please try again.</p></body></html>');
1689
- server.close();
1690
- reject(err);
1691
- });
1692
- } else {
1693
- res.writeHead(404);
1694
- res.end();
1818
+ if (error) {
1819
+ res.writeHead(200, { 'Content-Type': 'text/html' });
1820
+ res.end('<html><body><h2>Authentication failed</h2><p>You can close this tab and try again.</p></body></html>');
1821
+ finish(new Error(`Authentication failed: ${error}`));
1822
+ return;
1695
1823
  }
1824
+
1825
+ if (!code) {
1826
+ res.writeHead(200, { 'Content-Type': 'text/html' });
1827
+ res.end('<html><body><h2>Missing auth code</h2><p>You can close this tab and try again.</p></body></html>');
1828
+ finish(new Error('No auth code received from callback'));
1829
+ return;
1830
+ }
1831
+
1832
+ const redirectUri = `http://127.0.0.1:${server.address().port}/callback`;
1833
+ const params = new URLSearchParams();
1834
+ params.set('grant_type', 'authorization_code');
1835
+ params.set('code', code);
1836
+ params.set('redirect_uri', redirectUri);
1837
+ params.set('client_id', NEUS_OAUTH_CLIENT_ID);
1838
+ params.set('code_verifier', codeVerifier);
1839
+ params.set('resource', NEUS_MCP_RESOURCE);
1840
+
1841
+ fetch(NEUS_TOKEN_ENDPOINT, {
1842
+ method: 'POST',
1843
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
1844
+ body: params.toString(),
1845
+ signal: AbortSignal.timeout(15_000),
1846
+ })
1847
+ .then(tokenResp => tokenResp.json())
1848
+ .then(tokenJson => {
1849
+ if (!tokenJson.access_token) {
1850
+ res.writeHead(200, { 'Content-Type': 'text/html' });
1851
+ res.end('<html><body><h2>Token exchange failed</h2><p>Please try again.</p></body></html>');
1852
+ finish(new Error(tokenJson.error_description || tokenJson.error || 'Token exchange failed'));
1853
+ return;
1854
+ }
1855
+
1856
+ const accessToken = tokenJson.access_token;
1857
+ res.writeHead(200, { 'Content-Type': 'text/html' });
1858
+ res.end('<html><body><h2>Authenticated</h2><p>You can close this tab and return to your terminal.</p></body></html>');
1859
+
1860
+ const results = runClientOperations(browserManagedClients, scope, cwd, options.dryRun, client =>
1861
+ installClient(client, scope, accessToken, options.dryRun, cwd)
1862
+ );
1863
+ results.push(
1864
+ ...runClientOperations(hostManagedClients, scope, cwd, options.dryRun, () =>
1865
+ authCodex(scope, options.dryRun, cwd, options)
1866
+ )
1867
+ );
1868
+ const payload = {
1869
+ command: 'auth',
1870
+ scope,
1871
+ clients,
1872
+ accessKeyConfigured: true,
1873
+ authMethod: 'browser',
1874
+ results,
1875
+ hasErrors: results.some(result => result.error)
1876
+ };
1877
+ finish(null, payload);
1878
+ })
1879
+ .catch(err => {
1880
+ res.writeHead(200, { 'Content-Type': 'text/html' });
1881
+ res.end('<html><body><h2>Connection error</h2><p>Please try again.</p></body></html>');
1882
+ finish(err);
1883
+ });
1696
1884
  });
1697
1885
 
1698
1886
  server.listen(0, '127.0.0.1', () => {
@@ -1728,10 +1916,13 @@ async function runAuthBrowser(options) {
1728
1916
  });
1729
1917
 
1730
1918
  // Timeout after 5 minutes
1731
- setTimeout(() => {
1732
- server.close();
1733
- reject(new Error('Authentication timed out after 5 minutes. Try again.'));
1919
+ const timeout = setTimeout(() => {
1920
+ finish(new Error('Authentication timed out after 5 minutes. Try again.'));
1734
1921
  }, 5 * 60 * 1000);
1922
+
1923
+ server.on('close', () => {
1924
+ clearTimeout(timeout);
1925
+ });
1735
1926
  });
1736
1927
  }
1737
1928
 
@@ -1864,6 +2055,19 @@ async function runSetup(options) {
1864
2055
  return authResult || payload;
1865
2056
  }
1866
2057
 
2058
+ if (options.agent && !options.dryRun) {
2059
+ const mountKey = resolveLiveAccessKey(options, scope, cwd);
2060
+ if (mountKey) {
2061
+ await runMount({
2062
+ ...options,
2063
+ agentTarget: options.agent,
2064
+ apply: options.apply || 'cursor',
2065
+ json: false,
2066
+ live: true
2067
+ });
2068
+ }
2069
+ }
2070
+
1867
2071
  return payload;
1868
2072
  }
1869
2073
 
@@ -1945,6 +2149,7 @@ const ASSISTANT_EXAMPLE_PROMPTS = [
1945
2149
  'Use NEUS Verify before taking sensitive actions.',
1946
2150
  'Check whether I already have the required trust receipt.',
1947
2151
  'Verify this agent is trusted before it runs tools.',
2152
+ 'Mount my NEUS agent context with neus_agent_mount, then follow its scoped policy.',
1948
2153
  'Use NEUS Vault before storing or using secrets.',
1949
2154
  'Show the receipt for this verification.'
1950
2155
  ];
@@ -2004,7 +2209,29 @@ async function runDoctor(options) {
2004
2209
  payload.profileConnectable = Boolean(payload.mcp.authenticated);
2005
2210
  payload.hasErrors =
2006
2211
  payload.hasErrors || !payload.mcp.reachable || !payload.mcp.authenticated;
2212
+ try {
2213
+ const agentDoctor = await evaluateAgentMountDoctor(
2214
+ liveAccessKey,
2215
+ cwd,
2216
+ AbortSignal.timeout(20000)
2217
+ );
2218
+ payload.agentVerified = agentDoctor.agentVerified;
2219
+ payload.mountFilePresent = agentDoctor.mountFilePresent;
2220
+ payload.mountFileValid = agentDoctor.mountFileValid;
2221
+ payload.mountNeedsRefresh = agentDoctor.mountNeedsRefresh;
2222
+ payload.mountRefreshReason = agentDoctor.mountRefreshReason;
2223
+ payload.mountAgentId = agentDoctor.mountAgentId;
2224
+ payload.agentLinkStatus = agentDoctor.agentLinkStatus;
2225
+ payload.delegationExpired = agentDoctor.delegationExpired;
2226
+ payload.missingDelegation = agentDoctor.missingDelegation;
2227
+ } catch {
2228
+ payload.agentVerified = false;
2229
+ }
2007
2230
  }
2231
+ } else {
2232
+ const manifest = readMountManifest(cwd);
2233
+ payload.mountFilePresent = Boolean(manifest);
2234
+ payload.mountAgentId = manifest?.identity?.agentId || null;
2008
2235
  }
2009
2236
 
2010
2237
  if (options.json) {
@@ -2053,6 +2280,26 @@ async function runDoctor(options) {
2053
2280
  logStep('ok', 'profile', `connected${handle}${receipts}`);
2054
2281
  writeGuidanceLine('NEUS Verify is ready. Ask your assistant to verify trust before sensitive actions.');
2055
2282
  writeGuidanceLine('Run `npx -y -p @neus/sdk neus examples` for starter prompts.');
2283
+ if (payload.mountFilePresent) {
2284
+ logStep('ok', 'mount', payload.mountAgentId ? `project mount: ${payload.mountAgentId}` : 'project mount on file');
2285
+ }
2286
+ if (payload.mountNeedsRefresh) {
2287
+ const reason =
2288
+ payload.delegationExpired
2289
+ ? 'delegation expired'
2290
+ : payload.missingDelegation
2291
+ ? 'delegation missing on file'
2292
+ : 'mount stale';
2293
+ logStep('warn', 'mount', `${reason} — run \`neus mount ${payload.mountAgentId || '<agentId>'} --apply cursor\``);
2294
+ payload.hasErrors = true;
2295
+ } else if (payload.agentVerified) {
2296
+ logStep('ok', 'agent', 'identity and delegation on file');
2297
+ } else if (payload.mountAgentId || payload.mountFilePresent) {
2298
+ writeGuidanceLine(
2299
+ `Mounted agent is not fully linked yet. Run \`neus mount ${payload.mountAgentId || '<agentId>'} --apply cursor\` after auth.`
2300
+ );
2301
+ payload.hasErrors = true;
2302
+ }
2056
2303
  } else {
2057
2304
  logStep('warn', 'profile', 'live connection was not confirmed — run `neus auth`');
2058
2305
  }
@@ -2196,6 +2443,10 @@ async function main() {
2196
2443
  await runDoctor(options);
2197
2444
  return;
2198
2445
  }
2446
+ if (command === 'mount') {
2447
+ await runMount(options);
2448
+ return;
2449
+ }
2199
2450
  if (command === 'examples') {
2200
2451
  runExamples(options);
2201
2452
  return;