@neus/sdk 1.1.5 → 1.1.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.
package/cjs/client.cjs CHANGED
@@ -1530,6 +1530,11 @@ ${bytes.length}`;
1530
1530
  storeOriginalContent: typeof options?.storeOriginalContent === "boolean" ? options.storeOriginalContent : true
1531
1531
  };
1532
1532
  if (typeof options?.enableIpfs === "boolean") optionsPayload.enableIpfs = options.enableIpfs;
1533
+ if (options?.publishToHub === true) {
1534
+ optionsPayload.publishToHub = true;
1535
+ } else {
1536
+ delete optionsPayload.publishToHub;
1537
+ }
1533
1538
  const requestData = {
1534
1539
  verifierIds: normalizedVerifierIds,
1535
1540
  data,
@@ -1738,30 +1743,50 @@ ${bytes.length}`;
1738
1743
  }
1739
1744
  throw new ValidationError(`Failed to sign revocation: ${error.message}`);
1740
1745
  }
1741
- const res = await this._makeRequest("POST", `/api/v1/proofs/revoke-self/${qHash}`, {
1746
+ const json = await this._makeRequest("POST", `/api/v1/proofs/revoke-self/${qHash}`, {
1742
1747
  walletAddress: address,
1743
1748
  signature,
1744
1749
  signedTimestamp,
1745
1750
  ...signerIsEvm ? {} : { chain, signatureMethod }
1746
1751
  });
1747
- const json = await res.json();
1748
1752
  if (!json.success) {
1749
1753
  throw new ApiError(json.error?.message || "Failed to revoke proof", json.error);
1750
1754
  }
1751
1755
  return true;
1752
1756
  }
1757
+ _buildProofsByWalletQuery(options = {}) {
1758
+ const qs = [];
1759
+ if (options.limit) qs.push(`limit=${encodeURIComponent(String(options.limit))}`);
1760
+ const cursorRaw = options.cursor !== null && options.cursor !== void 0 ? String(options.cursor).trim() : "";
1761
+ if (cursorRaw) qs.push(`cursor=${encodeURIComponent(cursorRaw)}`);
1762
+ else if (options.offset !== void 0 && options.offset !== null) {
1763
+ qs.push(`offset=${encodeURIComponent(String(options.offset))}`);
1764
+ }
1765
+ if (options.q) qs.push(`q=${encodeURIComponent(String(options.q))}`);
1766
+ if (options.qHash) qs.push(`qHash=${encodeURIComponent(String(options.qHash).toLowerCase())}`);
1767
+ if (options.verifierId) qs.push(`verifierId=${encodeURIComponent(String(options.verifierId))}`);
1768
+ if (options.verifierIds) qs.push(`verifierIds=${encodeURIComponent(String(options.verifierIds))}`);
1769
+ if (options.tags) qs.push(`tags=${encodeURIComponent(String(options.tags))}`);
1770
+ if (options.tagPrefix) qs.push(`tagPrefix=${encodeURIComponent(String(options.tagPrefix))}`);
1771
+ if (options.tagContains) qs.push(`tagContains=${encodeURIComponent(String(options.tagContains))}`);
1772
+ if (options.tagPrefixesAll) qs.push(`tagPrefixesAll=${encodeURIComponent(String(options.tagPrefixesAll))}`);
1773
+ if (options.status) qs.push(`status=${encodeURIComponent(String(options.status))}`);
1774
+ if (options.appId) qs.push(`appId=${encodeURIComponent(String(options.appId))}`);
1775
+ if (options.chainCoverage) qs.push(`chainCoverage=${encodeURIComponent(String(options.chainCoverage))}`);
1776
+ if (options.privacyLevel) qs.push(`privacyLevel=${encodeURIComponent(String(options.privacyLevel))}`);
1777
+ if (options.includeHistory) qs.push("includeHistory=1");
1778
+ if (options.includeFacets) qs.push(`includeFacets=${encodeURIComponent(String(options.includeFacets))}`);
1779
+ if (options.visibility) qs.push(`visibility=${encodeURIComponent(String(options.visibility))}`);
1780
+ if (options.isPublicRead) qs.push("isPublicRead=1");
1781
+ return qs;
1782
+ }
1753
1783
  async getProofsByWallet(walletAddress, options = {}) {
1754
1784
  if (!walletAddress || typeof walletAddress !== "string") {
1755
1785
  throw new ValidationError("walletAddress is required");
1756
1786
  }
1757
1787
  const id = walletAddress.trim();
1758
1788
  const pathId = /^0x[a-fA-F0-9]{40}$/i.test(id) ? id.toLowerCase() : id;
1759
- const qs = [];
1760
- if (options.limit) qs.push(`limit=${encodeURIComponent(String(options.limit))}`);
1761
- const cursorRaw = options.cursor !== null && options.cursor !== void 0 ? String(options.cursor).trim() : "";
1762
- if (cursorRaw) qs.push(`cursor=${encodeURIComponent(cursorRaw)}`);
1763
- else if (options.offset) qs.push(`offset=${encodeURIComponent(String(options.offset))}`);
1764
- if (options.qHash) qs.push(`qHash=${encodeURIComponent(options.qHash.toLowerCase())}`);
1789
+ const qs = this._buildProofsByWalletQuery(options);
1765
1790
  const query = qs.length ? `?${qs.join("&")}` : "";
1766
1791
  const response = await this._makeRequest(
1767
1792
  "GET",
@@ -1774,10 +1799,11 @@ ${bytes.length}`;
1774
1799
  return {
1775
1800
  success: true,
1776
1801
  proofs: Array.isArray(proofs) ? proofs : [],
1777
- totalCount: response.data?.totalCount ?? proofs.length,
1802
+ totalCount: typeof response.data?.totalCount === "number" ? response.data.totalCount : null,
1778
1803
  hasMore: Boolean(response.data?.hasMore),
1779
1804
  nextOffset: response.data?.nextOffset ?? null,
1780
- nextCursor: typeof response.data?.nextCursor === "string" && response.data.nextCursor.trim() ? response.data.nextCursor.trim() : null
1805
+ nextCursor: typeof response.data?.nextCursor === "string" && response.data.nextCursor.trim() ? response.data.nextCursor.trim() : null,
1806
+ facets: response.data?.facets || null
1781
1807
  };
1782
1808
  }
1783
1809
  async getPrivateProofsByWallet(walletAddress, options = {}, wallet = null) {
@@ -1827,12 +1853,7 @@ ${bytes.length}`;
1827
1853
  }
1828
1854
  throw new ValidationError(`Failed to sign message: ${error.message}`);
1829
1855
  }
1830
- const qs = [];
1831
- if (options.limit) qs.push(`limit=${encodeURIComponent(String(options.limit))}`);
1832
- const cursorRaw = options.cursor !== null && options.cursor !== void 0 ? String(options.cursor).trim() : "";
1833
- if (cursorRaw) qs.push(`cursor=${encodeURIComponent(cursorRaw)}`);
1834
- else if (options.offset) qs.push(`offset=${encodeURIComponent(String(options.offset))}`);
1835
- if (options.qHash) qs.push(`qHash=${encodeURIComponent(options.qHash.toLowerCase())}`);
1856
+ const qs = this._buildProofsByWalletQuery(options);
1836
1857
  const query = qs.length ? `?${qs.join("&")}` : "";
1837
1858
  const response = await this._makeRequest("GET", `/api/v1/proofs/by-wallet/${encodeURIComponent(pathId)}${query}`, null, {
1838
1859
  "x-wallet-address": signerWalletAddress,
@@ -1847,7 +1868,7 @@ ${bytes.length}`;
1847
1868
  return {
1848
1869
  success: true,
1849
1870
  proofs: Array.isArray(proofs) ? proofs : [],
1850
- totalCount: response.data?.totalCount ?? proofs.length,
1871
+ totalCount: typeof response.data?.totalCount === "number" ? response.data.totalCount : null,
1851
1872
  hasMore: Boolean(response.data?.hasMore),
1852
1873
  nextOffset: response.data?.nextOffset ?? null,
1853
1874
  nextCursor: typeof response.data?.nextCursor === "string" && response.data.nextCursor.trim() ? response.data.nextCursor.trim() : null
@@ -1964,6 +1985,64 @@ ${bytes.length}`;
1964
1985
  }
1965
1986
  return response;
1966
1987
  }
1988
+ /**
1989
+ * Get the public snapshot of a published gate: requirements, charge, schedule,
1990
+ * checkout plan, and reward presence. Never returns the secret reward value —
1991
+ * that is delivered post-verify via fulfillGate().
1992
+ *
1993
+ * @param {string} gateId Published gate handle
1994
+ * @returns {Promise<object>} Public gate snapshot
1995
+ */
1996
+ async getGate(gateId) {
1997
+ const id = String(gateId || "").trim();
1998
+ if (!id || id.length > 80 || !/^[a-zA-Z0-9:_-]+$/.test(id)) {
1999
+ throw new ValidationError("Valid gateId is required");
2000
+ }
2001
+ const response = await this._makeRequest("GET", `/api/v1/profile/gates/${encodeURIComponent(id)}`);
2002
+ if (!response.success || !response.data?.gate) {
2003
+ throw new ApiError(`Gate lookup failed: ${response.error?.message || "Gate not found"}`, response.error);
2004
+ }
2005
+ return response.data.gate;
2006
+ }
2007
+ /**
2008
+ * Post-verify reward delivery for hosted gate checkout. Requires a verified
2009
+ * proof (qHash) for the gate; paid gates also require payment evidence
2010
+ * (paymentCheckoutSessionId for card, or paymentTxHash for USDC).
2011
+ *
2012
+ * @param {object} params
2013
+ * @param {string} params.gateId Published gate handle
2014
+ * @param {string} params.qHash Verified proof receipt id
2015
+ * @param {string} [params.walletAddress] Wallet bound to the proof (required without a session cookie)
2016
+ * @param {string} [params.paymentCheckoutSessionId] Stripe checkout session id (card rail)
2017
+ * @param {string} [params.paymentTxHash] USDC payment transaction hash (wallet rail)
2018
+ * @returns {Promise<object>} `{ success, data: { gateId, qHash, fulfillment, successReturnUrl? } }`
2019
+ */
2020
+ async fulfillGate(params = {}) {
2021
+ const gateId = String(params.gateId || "").trim();
2022
+ if (!gateId || gateId.length > 80 || !/^[a-zA-Z0-9:_-]+$/.test(gateId)) {
2023
+ throw new ValidationError("Valid gateId is required");
2024
+ }
2025
+ const qHash = String(params.qHash || "").trim();
2026
+ if (!/^0x[a-fA-F0-9]{64}$/.test(qHash)) {
2027
+ throw new ValidationError("Valid qHash is required");
2028
+ }
2029
+ const body = { qHash };
2030
+ const walletAddress = String(params.walletAddress || "").trim();
2031
+ if (walletAddress) body.walletAddress = walletAddress;
2032
+ const paymentCheckoutSessionId = String(params.paymentCheckoutSessionId || "").trim();
2033
+ if (paymentCheckoutSessionId) body.paymentCheckoutSessionId = paymentCheckoutSessionId;
2034
+ const paymentTxHash = String(params.paymentTxHash || "").trim();
2035
+ if (paymentTxHash) body.paymentTxHash = paymentTxHash;
2036
+ const response = await this._makeRequest(
2037
+ "POST",
2038
+ `/api/v1/profile/gates/${encodeURIComponent(gateId)}/fulfill`,
2039
+ body
2040
+ );
2041
+ if (!response.success) {
2042
+ throw new ApiError(`Gate fulfillment failed: ${response.error?.message || "Unknown error"}`, response.error);
2043
+ }
2044
+ return response;
2045
+ }
1967
2046
  async checkGate(params) {
1968
2047
  const { walletAddress, requirements, proofs: preloadedProofs } = params;
1969
2048
  if (!validateUniversalAddress(walletAddress)) {
package/cjs/index.cjs CHANGED
@@ -2223,6 +2223,11 @@ ${bytes.length}`;
2223
2223
  storeOriginalContent: typeof options?.storeOriginalContent === "boolean" ? options.storeOriginalContent : true
2224
2224
  };
2225
2225
  if (typeof options?.enableIpfs === "boolean") optionsPayload.enableIpfs = options.enableIpfs;
2226
+ if (options?.publishToHub === true) {
2227
+ optionsPayload.publishToHub = true;
2228
+ } else {
2229
+ delete optionsPayload.publishToHub;
2230
+ }
2226
2231
  const requestData = {
2227
2232
  verifierIds: normalizedVerifierIds,
2228
2233
  data,
@@ -2431,30 +2436,50 @@ ${bytes.length}`;
2431
2436
  }
2432
2437
  throw new ValidationError(`Failed to sign revocation: ${error.message}`);
2433
2438
  }
2434
- const res = await this._makeRequest("POST", `/api/v1/proofs/revoke-self/${qHash}`, {
2439
+ const json = await this._makeRequest("POST", `/api/v1/proofs/revoke-self/${qHash}`, {
2435
2440
  walletAddress: address,
2436
2441
  signature,
2437
2442
  signedTimestamp,
2438
2443
  ...signerIsEvm ? {} : { chain, signatureMethod }
2439
2444
  });
2440
- const json = await res.json();
2441
2445
  if (!json.success) {
2442
2446
  throw new ApiError(json.error?.message || "Failed to revoke proof", json.error);
2443
2447
  }
2444
2448
  return true;
2445
2449
  }
2450
+ _buildProofsByWalletQuery(options = {}) {
2451
+ const qs = [];
2452
+ if (options.limit) qs.push(`limit=${encodeURIComponent(String(options.limit))}`);
2453
+ const cursorRaw = options.cursor !== null && options.cursor !== void 0 ? String(options.cursor).trim() : "";
2454
+ if (cursorRaw) qs.push(`cursor=${encodeURIComponent(cursorRaw)}`);
2455
+ else if (options.offset !== void 0 && options.offset !== null) {
2456
+ qs.push(`offset=${encodeURIComponent(String(options.offset))}`);
2457
+ }
2458
+ if (options.q) qs.push(`q=${encodeURIComponent(String(options.q))}`);
2459
+ if (options.qHash) qs.push(`qHash=${encodeURIComponent(String(options.qHash).toLowerCase())}`);
2460
+ if (options.verifierId) qs.push(`verifierId=${encodeURIComponent(String(options.verifierId))}`);
2461
+ if (options.verifierIds) qs.push(`verifierIds=${encodeURIComponent(String(options.verifierIds))}`);
2462
+ if (options.tags) qs.push(`tags=${encodeURIComponent(String(options.tags))}`);
2463
+ if (options.tagPrefix) qs.push(`tagPrefix=${encodeURIComponent(String(options.tagPrefix))}`);
2464
+ if (options.tagContains) qs.push(`tagContains=${encodeURIComponent(String(options.tagContains))}`);
2465
+ if (options.tagPrefixesAll) qs.push(`tagPrefixesAll=${encodeURIComponent(String(options.tagPrefixesAll))}`);
2466
+ if (options.status) qs.push(`status=${encodeURIComponent(String(options.status))}`);
2467
+ if (options.appId) qs.push(`appId=${encodeURIComponent(String(options.appId))}`);
2468
+ if (options.chainCoverage) qs.push(`chainCoverage=${encodeURIComponent(String(options.chainCoverage))}`);
2469
+ if (options.privacyLevel) qs.push(`privacyLevel=${encodeURIComponent(String(options.privacyLevel))}`);
2470
+ if (options.includeHistory) qs.push("includeHistory=1");
2471
+ if (options.includeFacets) qs.push(`includeFacets=${encodeURIComponent(String(options.includeFacets))}`);
2472
+ if (options.visibility) qs.push(`visibility=${encodeURIComponent(String(options.visibility))}`);
2473
+ if (options.isPublicRead) qs.push("isPublicRead=1");
2474
+ return qs;
2475
+ }
2446
2476
  async getProofsByWallet(walletAddress, options = {}) {
2447
2477
  if (!walletAddress || typeof walletAddress !== "string") {
2448
2478
  throw new ValidationError("walletAddress is required");
2449
2479
  }
2450
2480
  const id = walletAddress.trim();
2451
2481
  const pathId = /^0x[a-fA-F0-9]{40}$/i.test(id) ? id.toLowerCase() : id;
2452
- const qs = [];
2453
- if (options.limit) qs.push(`limit=${encodeURIComponent(String(options.limit))}`);
2454
- const cursorRaw = options.cursor !== null && options.cursor !== void 0 ? String(options.cursor).trim() : "";
2455
- if (cursorRaw) qs.push(`cursor=${encodeURIComponent(cursorRaw)}`);
2456
- else if (options.offset) qs.push(`offset=${encodeURIComponent(String(options.offset))}`);
2457
- if (options.qHash) qs.push(`qHash=${encodeURIComponent(options.qHash.toLowerCase())}`);
2482
+ const qs = this._buildProofsByWalletQuery(options);
2458
2483
  const query = qs.length ? `?${qs.join("&")}` : "";
2459
2484
  const response = await this._makeRequest(
2460
2485
  "GET",
@@ -2467,10 +2492,11 @@ ${bytes.length}`;
2467
2492
  return {
2468
2493
  success: true,
2469
2494
  proofs: Array.isArray(proofs) ? proofs : [],
2470
- totalCount: response.data?.totalCount ?? proofs.length,
2495
+ totalCount: typeof response.data?.totalCount === "number" ? response.data.totalCount : null,
2471
2496
  hasMore: Boolean(response.data?.hasMore),
2472
2497
  nextOffset: response.data?.nextOffset ?? null,
2473
- nextCursor: typeof response.data?.nextCursor === "string" && response.data.nextCursor.trim() ? response.data.nextCursor.trim() : null
2498
+ nextCursor: typeof response.data?.nextCursor === "string" && response.data.nextCursor.trim() ? response.data.nextCursor.trim() : null,
2499
+ facets: response.data?.facets || null
2474
2500
  };
2475
2501
  }
2476
2502
  async getPrivateProofsByWallet(walletAddress, options = {}, wallet = null) {
@@ -2520,12 +2546,7 @@ ${bytes.length}`;
2520
2546
  }
2521
2547
  throw new ValidationError(`Failed to sign message: ${error.message}`);
2522
2548
  }
2523
- const qs = [];
2524
- if (options.limit) qs.push(`limit=${encodeURIComponent(String(options.limit))}`);
2525
- const cursorRaw = options.cursor !== null && options.cursor !== void 0 ? String(options.cursor).trim() : "";
2526
- if (cursorRaw) qs.push(`cursor=${encodeURIComponent(cursorRaw)}`);
2527
- else if (options.offset) qs.push(`offset=${encodeURIComponent(String(options.offset))}`);
2528
- if (options.qHash) qs.push(`qHash=${encodeURIComponent(options.qHash.toLowerCase())}`);
2549
+ const qs = this._buildProofsByWalletQuery(options);
2529
2550
  const query = qs.length ? `?${qs.join("&")}` : "";
2530
2551
  const response = await this._makeRequest("GET", `/api/v1/proofs/by-wallet/${encodeURIComponent(pathId)}${query}`, null, {
2531
2552
  "x-wallet-address": signerWalletAddress,
@@ -2540,7 +2561,7 @@ ${bytes.length}`;
2540
2561
  return {
2541
2562
  success: true,
2542
2563
  proofs: Array.isArray(proofs) ? proofs : [],
2543
- totalCount: response.data?.totalCount ?? proofs.length,
2564
+ totalCount: typeof response.data?.totalCount === "number" ? response.data.totalCount : null,
2544
2565
  hasMore: Boolean(response.data?.hasMore),
2545
2566
  nextOffset: response.data?.nextOffset ?? null,
2546
2567
  nextCursor: typeof response.data?.nextCursor === "string" && response.data.nextCursor.trim() ? response.data.nextCursor.trim() : null
@@ -2657,6 +2678,64 @@ ${bytes.length}`;
2657
2678
  }
2658
2679
  return response;
2659
2680
  }
2681
+ /**
2682
+ * Get the public snapshot of a published gate: requirements, charge, schedule,
2683
+ * checkout plan, and reward presence. Never returns the secret reward value —
2684
+ * that is delivered post-verify via fulfillGate().
2685
+ *
2686
+ * @param {string} gateId Published gate handle
2687
+ * @returns {Promise<object>} Public gate snapshot
2688
+ */
2689
+ async getGate(gateId) {
2690
+ const id = String(gateId || "").trim();
2691
+ if (!id || id.length > 80 || !/^[a-zA-Z0-9:_-]+$/.test(id)) {
2692
+ throw new ValidationError("Valid gateId is required");
2693
+ }
2694
+ const response = await this._makeRequest("GET", `/api/v1/profile/gates/${encodeURIComponent(id)}`);
2695
+ if (!response.success || !response.data?.gate) {
2696
+ throw new ApiError(`Gate lookup failed: ${response.error?.message || "Gate not found"}`, response.error);
2697
+ }
2698
+ return response.data.gate;
2699
+ }
2700
+ /**
2701
+ * Post-verify reward delivery for hosted gate checkout. Requires a verified
2702
+ * proof (qHash) for the gate; paid gates also require payment evidence
2703
+ * (paymentCheckoutSessionId for card, or paymentTxHash for USDC).
2704
+ *
2705
+ * @param {object} params
2706
+ * @param {string} params.gateId Published gate handle
2707
+ * @param {string} params.qHash Verified proof receipt id
2708
+ * @param {string} [params.walletAddress] Wallet bound to the proof (required without a session cookie)
2709
+ * @param {string} [params.paymentCheckoutSessionId] Stripe checkout session id (card rail)
2710
+ * @param {string} [params.paymentTxHash] USDC payment transaction hash (wallet rail)
2711
+ * @returns {Promise<object>} `{ success, data: { gateId, qHash, fulfillment, successReturnUrl? } }`
2712
+ */
2713
+ async fulfillGate(params = {}) {
2714
+ const gateId = String(params.gateId || "").trim();
2715
+ if (!gateId || gateId.length > 80 || !/^[a-zA-Z0-9:_-]+$/.test(gateId)) {
2716
+ throw new ValidationError("Valid gateId is required");
2717
+ }
2718
+ const qHash = String(params.qHash || "").trim();
2719
+ if (!/^0x[a-fA-F0-9]{64}$/.test(qHash)) {
2720
+ throw new ValidationError("Valid qHash is required");
2721
+ }
2722
+ const body = { qHash };
2723
+ const walletAddress = String(params.walletAddress || "").trim();
2724
+ if (walletAddress) body.walletAddress = walletAddress;
2725
+ const paymentCheckoutSessionId = String(params.paymentCheckoutSessionId || "").trim();
2726
+ if (paymentCheckoutSessionId) body.paymentCheckoutSessionId = paymentCheckoutSessionId;
2727
+ const paymentTxHash = String(params.paymentTxHash || "").trim();
2728
+ if (paymentTxHash) body.paymentTxHash = paymentTxHash;
2729
+ const response = await this._makeRequest(
2730
+ "POST",
2731
+ `/api/v1/profile/gates/${encodeURIComponent(gateId)}/fulfill`,
2732
+ body
2733
+ );
2734
+ if (!response.success) {
2735
+ throw new ApiError(`Gate fulfillment failed: ${response.error?.message || "Unknown error"}`, response.error);
2736
+ }
2737
+ return response;
2738
+ }
2660
2739
  async checkGate(params) {
2661
2740
  const { walletAddress, requirements, proofs: preloadedProofs } = params;
2662
2741
  if (!validateUniversalAddress(walletAddress)) {
package/cli/neus.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { spawnSync } from 'node:child_process';
2
+ import { exec, spawnSync } from 'node:child_process';
3
3
  import { createHash, randomBytes } from 'node:crypto';
4
4
  import fs from 'node:fs';
5
5
  import os from 'node:os';
@@ -12,6 +12,13 @@ import {
12
12
  } from '../mcp-hosts.js';
13
13
 
14
14
  const __cliDir = path.dirname(fileURLToPath(import.meta.url));
15
+ const CLI_PACKAGE_VERSION = (() => {
16
+ try {
17
+ return JSON.parse(fs.readFileSync(path.join(__cliDir, '..', 'package.json'), 'utf8')).version;
18
+ } catch {
19
+ return '0.0.0';
20
+ }
21
+ })();
15
22
 
16
23
  const NEUS_APP_URL = 'https://neus.network';
17
24
  const NEUS_TOKEN_ENDPOINT = 'https://neus.network/api/v1/auth/mcp/token';
@@ -137,6 +144,7 @@ function describeClientResult(command, result) {
137
144
  }
138
145
  if (result.changed) return 'updated';
139
146
  if (result.authConfigured) return 'signed in';
147
+ if (result.configured) return 'ready';
140
148
  return 'ready';
141
149
  }
142
150
 
@@ -296,6 +304,7 @@ function envAccessKey() {
296
304
 
297
305
  /** --access-key flag, else NEUS_ACCESS_KEY from the environment, else browser sign-in. */
298
306
  function resolveAccessKey(options) {
307
+ if (options?.oauth) return '';
299
308
  const explicit = String(options.accessKey || '').trim();
300
309
  if (explicit) return explicit;
301
310
  return envAccessKey();
@@ -307,6 +316,7 @@ function resolveLiveAccessKey(options, scope, cwd) {
307
316
  if (explicit) return explicit;
308
317
  const installed = readInstalledAccessKey(scope, cwd);
309
318
  if (installed) return installed;
319
+ if (options?.oauth) return '';
310
320
  return envAccessKey();
311
321
  }
312
322
 
@@ -579,7 +589,8 @@ function parseArgs(argv) {
579
589
  live: false,
580
590
  json: false,
581
591
  dryRun: false,
582
- project: false
592
+ project: false,
593
+ oauth: false
583
594
  };
584
595
 
585
596
  for (let index = 1; index < argv.length; index += 1) {
@@ -635,6 +646,10 @@ function parseArgs(argv) {
635
646
  index += 1;
636
647
  continue;
637
648
  }
649
+ if (token === '--oauth') {
650
+ options.oauth = true;
651
+ continue;
652
+ }
638
653
  if (token === '--help' || token === '-h') {
639
654
  return { command: 'help', options };
640
655
  }
@@ -668,6 +683,7 @@ function printUsage(exitCode = 0) {
668
683
  ' --client <name[,name]> Limit setup to claude, codex, cursor, or vscode',
669
684
  ' --project Write shared project config instead of user config',
670
685
  ' --access-key <npk_...> Override profile access key (else uses NEUS_ACCESS_KEY if set)',
686
+ ' --oauth Force browser OAuth (ignore NEUS_ACCESS_KEY in the environment)',
671
687
  ' --from <source> Import source: auto, cursor, claude-code, or claude-desktop',
672
688
  ' --to <format> Export format: manifest or json',
673
689
  ' --output <path> Write exported manifest to a specific path',
@@ -1383,7 +1399,7 @@ async function runLiveMcpDiagnostics(accessKey) {
1383
1399
  params: {
1384
1400
  protocolVersion: '2025-11-25',
1385
1401
  capabilities: {},
1386
- clientInfo: { name: 'neus-cli', version: '1.1.5' }
1402
+ clientInfo: { name: 'neus-cli', version: CLI_PACKAGE_VERSION }
1387
1403
  },
1388
1404
  accessKey,
1389
1405
  signal: controller.signal
@@ -1699,9 +1715,12 @@ async function runAuthBrowser(options) {
1699
1715
  logStep('next', 'wait', 'finish sign-in in the browser');
1700
1716
  }
1701
1717
 
1702
- const { exec } = require('node:child_process');
1703
- const openCmd = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
1704
- exec(`${openCmd} "${authUrl}"`, err => {
1718
+ const openCommand = process.platform === 'win32'
1719
+ ? `cmd /c start "" "${authUrl.replace(/"/g, '\\"')}"`
1720
+ : process.platform === 'darwin'
1721
+ ? `open "${authUrl.replace(/"/g, '\\"')}"`
1722
+ : `xdg-open "${authUrl.replace(/"/g, '\\"')}"`;
1723
+ exec(openCommand, { shell: true }, err => {
1705
1724
  if (err && !options.json) {
1706
1725
  logStep('warn', 'browser', 'open the URL above manually');
1707
1726
  }
@@ -1816,6 +1835,12 @@ async function runSetup(options) {
1816
1835
  }
1817
1836
 
1818
1837
  if (options.json) {
1838
+ payload.authRequired = !accessKey && !options.dryRun;
1839
+ if (payload.authRequired) {
1840
+ payload.nextCommand = clients.length === 1 && clients[0] === 'codex'
1841
+ ? 'neus auth --client codex'
1842
+ : 'neus auth';
1843
+ }
1819
1844
  printJson(payload);
1820
1845
  return payload;
1821
1846
  }