@neus/sdk 1.0.10 → 1.0.12

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
@@ -1,15 +1,46 @@
1
1
  # @neus/sdk
2
2
 
3
- Create, check, and reuse NEUS trust receipts from apps.
3
+ Create, check, and reuse NEUS trust receipts from apps and backends. The same package ships the **`neus`** CLI for wiring **hosted NEUS MCP** into editors and agents.
4
4
 
5
- NEUS turns signed claims, ownership checks, account links, access rules, and verification results into portable receipts your app can store, display, and check later.
5
+ NEUS makes trust portable across the internet so people, apps, and AI agents can prove what is real before access, payout, or execution.
6
6
 
7
- ## Install
7
+ ## Install (library)
8
8
 
9
9
  ```bash
10
10
  npm install @neus/sdk
11
11
  ```
12
12
 
13
+ ## Connect editors and assistants (MCP)
14
+
15
+ Use one command to merge NEUS into Cursor, VS Code, and Claude Code when those tools are detected. No separate NEUS editor extension is required.
16
+
17
+ ```bash
18
+ npx -y -p @neus/sdk neus setup
19
+ ```
20
+
21
+ Add your NEUS Profile access key in the same step (needed for account-aware tools such as `neus_me`):
22
+
23
+ ```bash
24
+ npx -y -p @neus/sdk neus setup --access-key <npk_...>
25
+ ```
26
+
27
+ Check configuration and connectivity:
28
+
29
+ ```bash
30
+ npx -y -p @neus/sdk neus doctor
31
+ ```
32
+
33
+ Create keys under **Profile → Account → Access keys** on [neus.network](https://neus.network/profile?tab=account). Never put access keys in browser bundles or public repos.
34
+
35
+ | Topic | Link |
36
+ | --- | --- |
37
+ | Setup, JSON snippets, and headers | [MCP setup](https://docs.neus.network/mcp/setup) |
38
+ | Tools and session order | [MCP overview](https://docs.neus.network/mcp/overview) |
39
+ | Discovery URLs | [Discovery and endpoints](https://docs.neus.network/mcp/endpoints) |
40
+ | Optional Claude Code plugin + skill | [NEUS for Claude Code](https://docs.neus.network/mcp/claude-code-marketplace) |
41
+
42
+ Prefer `neus setup` over hand-editing config files so every host stays on **`https://mcp.neus.network/mcp`**.
43
+
13
44
  ## What you can build
14
45
 
15
46
  - Issue a proof that a user, wallet, org, app, file, release, profile, or result belongs to someone
@@ -27,7 +58,7 @@ import { getHostedCheckoutUrl } from '@neus/sdk';
27
58
 
28
59
  const url = getHostedCheckoutUrl({
29
60
  verifiers: ['ownership-basic'],
30
- returnUrl: 'https://yourapp.com/neus/callback'
61
+ returnUrl: 'https://yourapp.com/auth/callback'
31
62
  });
32
63
 
33
64
  window.location.assign(url);
@@ -35,11 +66,11 @@ window.location.assign(url);
35
66
 
36
67
  After completion, NEUS redirects back with a `qHash`.
37
68
 
38
- Store that `qHash` in your database next to your user, workspace, project, claim, listing, or record.
69
+ Store that `qHash` with your user record.
39
70
 
40
71
  ## Create a signed receipt directly
41
72
 
42
- Use this when your app already controls the signing flow.
73
+ When your app handles signing. This example is EVM. For non-EVM wallets, pass the provider explicitly and include `chain` as a CAIP-2 value; see the CAIP-380 standards page in the docs.
43
74
 
44
75
  ```js
45
76
  import { NeusClient } from '@neus/sdk';
@@ -64,7 +95,7 @@ const proof = await client.verify({
64
95
  title: 'Source record'
65
96
  }
66
97
  },
67
- wallet: window.ethereum
98
+ wallet: window.ethereum // EVM provider
68
99
  });
69
100
 
70
101
  console.log(proof.qHash);
@@ -126,7 +157,7 @@ if (!result.data?.eligible) {
126
157
  }
127
158
  ```
128
159
 
129
- Access keys are only for trusted server, IDE, or agent environments. Never ship access keys in browser code.
160
+ Never ship access keys in browser code.
130
161
 
131
162
  ## Core methods
132
163
 
@@ -153,20 +184,20 @@ const client = new NeusClient({
153
184
  `appId` is public attribution for your app.
154
185
  `apiKey` / `npk_*` is optional and server-side only.
155
186
 
156
- ## Optional MCP setup
157
-
158
- Use MCP when you want IDEs, assistants, or agents to inspect receipts, check proof state, or work with NEUS tools.
187
+ ## MCP: step-by-step (alternative to `setup`)
159
188
 
160
189
  ```bash
161
190
  npx -y -p @neus/sdk neus init
162
191
  ```
163
192
 
164
- Add account-aware/private access only when needed:
193
+ Add or rotate a Profile access key on an existing install:
165
194
 
166
195
  ```bash
167
196
  npx -y -p @neus/sdk neus auth --access-key <npk_...>
168
197
  ```
169
198
 
199
+ Claude Code (optional): add marketplace `https://github.com/neus/network`, install **`neus-mcp@neus`**, then run **`neus setup`** so your key matches every editor. See [NEUS for Claude Code](https://docs.neus.network/mcp/claude-code-marketplace).
200
+
170
201
  ## Docs
171
202
 
172
203
  - Quickstart: https://docs.neus.network/quickstart
package/SECURITY.md CHANGED
@@ -20,7 +20,7 @@ Treat **wallet signatures** and **API keys** as secrets. Do not log them, expose
20
20
 
21
21
  **`VerifyGate`** create mode also defaults to **private**.
22
22
 
23
- Use public visibility only when you intentionally need proof reuse without owner-authenticated access:
23
+ Use public visibility only when you need proof reuse without owner-authenticated access:
24
24
 
25
25
  - unlisted public: `privacyLevel: 'public'`, `publicDisplay: false`
26
26
  - listed public: `privacyLevel: 'public'`, `publicDisplay: true`
package/cjs/client.cjs CHANGED
@@ -421,6 +421,11 @@ function normalizeBrowserSignerString(raw) {
421
421
  }
422
422
  return null;
423
423
  }
424
+ function isPlaceholderNeusSignature(signature) {
425
+ const s = typeof signature === "string" ? signature.trim() : "";
426
+ if (!s) return true;
427
+ return /^0x0+$/i.test(s);
428
+ }
424
429
  var validateVerifierData = (verifierId, data) => {
425
430
  if (!data || typeof data !== "object") {
426
431
  return { valid: false, error: "Data object is required" };
@@ -797,7 +802,7 @@ var NeusClient = class {
797
802
  }
798
803
  _getDefaultBrowserWallet() {
799
804
  if (typeof window === "undefined") return null;
800
- return window.ethereum || window.solana || window.phantom && window.phantom.solana || null;
805
+ return window.ethereum || null;
801
806
  }
802
807
  async _buildPrivateGateAuth({ address, wallet, chain, signatureMethod } = {}) {
803
808
  const providerWallet = wallet || this._getDefaultBrowserWallet();
@@ -922,8 +927,57 @@ var NeusClient = class {
922
927
  ...label ? { label } : {}
923
928
  };
924
929
  }
930
+ async verifyFromApp(params) {
931
+ if (!params || typeof params !== "object") {
932
+ throw new ValidationError("verifyFromApp requires a params object");
933
+ }
934
+ const {
935
+ user,
936
+ verifier = "ownership-basic",
937
+ content,
938
+ data: explicitData = null,
939
+ options = {}
940
+ } = params;
941
+ if (!user || typeof user !== "object") {
942
+ throw new ValidationError("verifyFromApp requires user object");
943
+ }
944
+ const walletAddress = user.walletAddress || user.address || user.identity;
945
+ if (!walletAddress || typeof walletAddress !== "string") {
946
+ throw new ValidationError("verifyFromApp requires user.walletAddress");
947
+ }
948
+ const delegationQHash = this.config.appLinkQHash || typeof process !== "undefined" && process.env && process.env.NEUS_APP_LINK_QHASH || null;
949
+ let data;
950
+ if (explicitData && typeof explicitData === "object") {
951
+ data = { owner: walletAddress, ...explicitData };
952
+ } else if (content && typeof content === "object") {
953
+ data = {
954
+ owner: walletAddress,
955
+ content: JSON.stringify(content),
956
+ contentType: typeof content.contentType === "string" ? content.contentType : "application/json",
957
+ reference: content.reference || { type: "other" }
958
+ };
959
+ } else if (typeof content === "string") {
960
+ data = {
961
+ owner: walletAddress,
962
+ content,
963
+ reference: { type: "other" }
964
+ };
965
+ } else {
966
+ data = { owner: walletAddress, reference: { type: "other" } };
967
+ }
968
+ const signedTimestamp = Date.now();
969
+ const verifierIds = [verifier];
970
+ return this.verify({
971
+ verifierIds,
972
+ data,
973
+ walletAddress,
974
+ signedTimestamp,
975
+ ...delegationQHash ? { delegationQHash } : {},
976
+ options
977
+ });
978
+ }
925
979
  async verify(params) {
926
- if ((!params?.signature || !params?.walletAddress) && (params?.verifier || params?.content || params?.data)) {
980
+ if ((isPlaceholderNeusSignature(params?.signature) || !params?.signature || !params?.walletAddress) && (params?.verifier || params?.content || params?.data)) {
927
981
  const { content, verifier = "ownership-basic", data: data2 = null, wallet = null, options: options2 = {} } = params;
928
982
  if (verifier === "ownership-basic" && !data2 && (!content || typeof content !== "string")) {
929
983
  throw new ValidationError("content is required and must be a string (or use data param with owner + reference)");
@@ -976,7 +1030,7 @@ var NeusClient = class {
976
1030
  }
977
1031
  } else {
978
1032
  if (typeof window === "undefined" || !window.ethereum) {
979
- throw new ConfigurationError("No Web3 wallet detected. Please install MetaMask or provide wallet parameter.");
1033
+ throw new ConfigurationError("No EVM browser wallet detected. Provide wallet explicitly for non-EVM flows and include chain as a CAIP-2 value.");
980
1034
  }
981
1035
  await window.ethereum.request({ method: "eth_requestAccounts" });
982
1036
  provider = window.ethereum;
@@ -1264,13 +1318,15 @@ ${bytes.length}`;
1264
1318
  verifierIds,
1265
1319
  data,
1266
1320
  walletAddress,
1267
- signature,
1321
+ signature: rawSignature,
1268
1322
  signedTimestamp,
1269
1323
  chainId,
1270
1324
  chain,
1271
1325
  signatureMethod,
1326
+ delegationQHash,
1272
1327
  options = {}
1273
1328
  } = params;
1329
+ const signature = isPlaceholderNeusSignature(rawSignature) ? void 0 : rawSignature;
1274
1330
  const resolvedChainId = chainId || (chain ? null : NEUS_CONSTANTS.HUB_CHAIN_ID);
1275
1331
  const normalizeVerifierId = (id) => {
1276
1332
  if (typeof id !== "string") return id;
@@ -1287,8 +1343,11 @@ ${bytes.length}`;
1287
1343
  if (!walletAddress || typeof walletAddress !== "string") {
1288
1344
  throw new ValidationError("walletAddress is required");
1289
1345
  }
1290
- if (!signature) {
1291
- throw new ValidationError("signature is required");
1346
+ const hasAppAttribution = typeof this.config.appId === "string" && this.config.appId.trim().length > 0;
1347
+ if (!signature && !delegationQHash && !hasAppAttribution) {
1348
+ throw new ValidationError(
1349
+ "signature, delegationQHash, or NeusClient config appId (sent as X-Neus-App) is required"
1350
+ );
1292
1351
  }
1293
1352
  if (!signedTimestamp || typeof signedTimestamp !== "number") {
1294
1353
  throw new ValidationError("signedTimestamp is required");
@@ -1314,11 +1373,12 @@ ${bytes.length}`;
1314
1373
  verifierIds: normalizedVerifierIds,
1315
1374
  data,
1316
1375
  walletAddress,
1317
- signature,
1376
+ ...signature ? { signature } : {},
1318
1377
  signedTimestamp,
1319
1378
  ...resolvedChainId !== null && { chainId: resolvedChainId },
1320
1379
  ...chain && { chain },
1321
1380
  ...signatureMethod && { signatureMethod },
1381
+ ...delegationQHash && { delegationQHash },
1322
1382
  options: optionsPayload
1323
1383
  };
1324
1384
  const response = await this._makeRequest("POST", "/api/v1/verification", requestData);
package/cjs/index.cjs CHANGED
@@ -1081,6 +1081,11 @@ function normalizeBrowserSignerString(raw) {
1081
1081
  }
1082
1082
  return null;
1083
1083
  }
1084
+ function isPlaceholderNeusSignature(signature) {
1085
+ const s = typeof signature === "string" ? signature.trim() : "";
1086
+ if (!s) return true;
1087
+ return /^0x0+$/i.test(s);
1088
+ }
1084
1089
  var FALLBACK_PUBLIC_VERIFIER_CATALOG, EVM_ADDRESS_RE, WALLET_LINK_RELATIONSHIP_TYPES, validateVerifierData, NeusClient;
1085
1090
  var init_client = __esm({
1086
1091
  "client.js"() {
@@ -1481,7 +1486,7 @@ var init_client = __esm({
1481
1486
  }
1482
1487
  _getDefaultBrowserWallet() {
1483
1488
  if (typeof window === "undefined") return null;
1484
- return window.ethereum || window.solana || window.phantom && window.phantom.solana || null;
1489
+ return window.ethereum || null;
1485
1490
  }
1486
1491
  async _buildPrivateGateAuth({ address, wallet, chain, signatureMethod } = {}) {
1487
1492
  const providerWallet = wallet || this._getDefaultBrowserWallet();
@@ -1606,8 +1611,57 @@ var init_client = __esm({
1606
1611
  ...label ? { label } : {}
1607
1612
  };
1608
1613
  }
1614
+ async verifyFromApp(params) {
1615
+ if (!params || typeof params !== "object") {
1616
+ throw new ValidationError("verifyFromApp requires a params object");
1617
+ }
1618
+ const {
1619
+ user,
1620
+ verifier = "ownership-basic",
1621
+ content,
1622
+ data: explicitData = null,
1623
+ options = {}
1624
+ } = params;
1625
+ if (!user || typeof user !== "object") {
1626
+ throw new ValidationError("verifyFromApp requires user object");
1627
+ }
1628
+ const walletAddress = user.walletAddress || user.address || user.identity;
1629
+ if (!walletAddress || typeof walletAddress !== "string") {
1630
+ throw new ValidationError("verifyFromApp requires user.walletAddress");
1631
+ }
1632
+ const delegationQHash = this.config.appLinkQHash || typeof process !== "undefined" && process.env && process.env.NEUS_APP_LINK_QHASH || null;
1633
+ let data;
1634
+ if (explicitData && typeof explicitData === "object") {
1635
+ data = { owner: walletAddress, ...explicitData };
1636
+ } else if (content && typeof content === "object") {
1637
+ data = {
1638
+ owner: walletAddress,
1639
+ content: JSON.stringify(content),
1640
+ contentType: typeof content.contentType === "string" ? content.contentType : "application/json",
1641
+ reference: content.reference || { type: "other" }
1642
+ };
1643
+ } else if (typeof content === "string") {
1644
+ data = {
1645
+ owner: walletAddress,
1646
+ content,
1647
+ reference: { type: "other" }
1648
+ };
1649
+ } else {
1650
+ data = { owner: walletAddress, reference: { type: "other" } };
1651
+ }
1652
+ const signedTimestamp = Date.now();
1653
+ const verifierIds = [verifier];
1654
+ return this.verify({
1655
+ verifierIds,
1656
+ data,
1657
+ walletAddress,
1658
+ signedTimestamp,
1659
+ ...delegationQHash ? { delegationQHash } : {},
1660
+ options
1661
+ });
1662
+ }
1609
1663
  async verify(params) {
1610
- if ((!params?.signature || !params?.walletAddress) && (params?.verifier || params?.content || params?.data)) {
1664
+ if ((isPlaceholderNeusSignature(params?.signature) || !params?.signature || !params?.walletAddress) && (params?.verifier || params?.content || params?.data)) {
1611
1665
  const { content, verifier = "ownership-basic", data: data2 = null, wallet = null, options: options2 = {} } = params;
1612
1666
  if (verifier === "ownership-basic" && !data2 && (!content || typeof content !== "string")) {
1613
1667
  throw new ValidationError("content is required and must be a string (or use data param with owner + reference)");
@@ -1660,7 +1714,7 @@ var init_client = __esm({
1660
1714
  }
1661
1715
  } else {
1662
1716
  if (typeof window === "undefined" || !window.ethereum) {
1663
- throw new ConfigurationError("No Web3 wallet detected. Please install MetaMask or provide wallet parameter.");
1717
+ throw new ConfigurationError("No EVM browser wallet detected. Provide wallet explicitly for non-EVM flows and include chain as a CAIP-2 value.");
1664
1718
  }
1665
1719
  await window.ethereum.request({ method: "eth_requestAccounts" });
1666
1720
  provider = window.ethereum;
@@ -1948,13 +2002,15 @@ ${bytes.length}`;
1948
2002
  verifierIds,
1949
2003
  data,
1950
2004
  walletAddress,
1951
- signature,
2005
+ signature: rawSignature,
1952
2006
  signedTimestamp,
1953
2007
  chainId,
1954
2008
  chain,
1955
2009
  signatureMethod,
2010
+ delegationQHash,
1956
2011
  options = {}
1957
2012
  } = params;
2013
+ const signature = isPlaceholderNeusSignature(rawSignature) ? void 0 : rawSignature;
1958
2014
  const resolvedChainId = chainId || (chain ? null : NEUS_CONSTANTS.HUB_CHAIN_ID);
1959
2015
  const normalizeVerifierId = (id) => {
1960
2016
  if (typeof id !== "string") return id;
@@ -1971,8 +2027,11 @@ ${bytes.length}`;
1971
2027
  if (!walletAddress || typeof walletAddress !== "string") {
1972
2028
  throw new ValidationError("walletAddress is required");
1973
2029
  }
1974
- if (!signature) {
1975
- throw new ValidationError("signature is required");
2030
+ const hasAppAttribution = typeof this.config.appId === "string" && this.config.appId.trim().length > 0;
2031
+ if (!signature && !delegationQHash && !hasAppAttribution) {
2032
+ throw new ValidationError(
2033
+ "signature, delegationQHash, or NeusClient config appId (sent as X-Neus-App) is required"
2034
+ );
1976
2035
  }
1977
2036
  if (!signedTimestamp || typeof signedTimestamp !== "number") {
1978
2037
  throw new ValidationError("signedTimestamp is required");
@@ -1998,11 +2057,12 @@ ${bytes.length}`;
1998
2057
  verifierIds: normalizedVerifierIds,
1999
2058
  data,
2000
2059
  walletAddress,
2001
- signature,
2060
+ ...signature ? { signature } : {},
2002
2061
  signedTimestamp,
2003
2062
  ...resolvedChainId !== null && { chainId: resolvedChainId },
2004
2063
  ...chain && { chain },
2005
2064
  ...signatureMethod && { signatureMethod },
2065
+ ...delegationQHash && { delegationQHash },
2006
2066
  options: optionsPayload
2007
2067
  };
2008
2068
  const response = await this._makeRequest("POST", "/api/v1/verification", requestData);
package/cli/neus.mjs CHANGED
@@ -203,16 +203,18 @@ function printUsage(exitCode = 0) {
203
203
  'Usage: neus <command> [options]',
204
204
  '',
205
205
  'Commands:',
206
+ ' setup One-command: run init, then auth if --access-key is provided',
206
207
  ' init Configure supported MCP clients automatically',
207
208
  ' auth Add or update a personal access key for NEUS MCP',
208
209
  ' status Show current NEUS MCP setup',
210
+ ' doctor Deep check: config status, profile connection, agent verification',
209
211
  ' help Show this message',
210
212
  '',
211
213
  'Options:',
212
214
  ' --client <name[,name]> Limit setup to claude, cursor, or vscode',
213
215
  ' --project Write shared project config instead of user config',
214
216
  ' --access-key <npk_...> Configure Bearer auth for personal account tools',
215
- ' --json Emit machine-readable output',
217
+ ' --json Print JSON output',
216
218
  ' --dry-run Preview changes without writing files',
217
219
  ];
218
220
  const stream = exitCode === 0 ? process.stdout : process.stderr;
@@ -246,8 +248,8 @@ function ensureClientSelection(scope, clients) {
246
248
  }
247
249
 
248
250
  function ensureSafeAuth(command, scope, accessKey) {
249
- if (command === 'auth' && scope !== 'user') {
250
- throw new Error('`neus auth` only supports user scope so access keys never land in shared project config.');
251
+ if ((command === 'auth' || command === 'setup') && scope !== 'user') {
252
+ throw new Error('`neus ${command}` only supports user scope so access keys never land in shared project config.');
251
253
  }
252
254
  if (scope === 'project' && accessKey) {
253
255
  throw new Error('Access keys are only supported in user scope. Remove --project or omit --access-key.');
@@ -256,7 +258,7 @@ function ensureSafeAuth(command, scope, accessKey) {
256
258
 
257
259
  function buildCursorServer(accessKey) {
258
260
  return {
259
- type: 'streamableHttp',
261
+ type: 'http',
260
262
  url: NEUS_MCP_URL,
261
263
  ...(accessKey ? { headers: { Authorization: `Bearer ${accessKey}` } } : {}),
262
264
  };
@@ -548,6 +550,14 @@ function printResultSummary(command, scope, results, accessKey) {
548
550
  if (command === 'init' && !accessKey) {
549
551
  lines.push(`Account tools stay optional. Add personal auth later with: neus auth --access-key <npk_...>`);
550
552
  }
553
+ if (command === 'init' || command === 'setup') {
554
+ lines.push(
555
+ 'Claude Code (optional): plugin neus-mcp@neus + docs — https://docs.neus.network/mcp/claude-code-marketplace',
556
+ );
557
+ lines.push(
558
+ 'Cursor / VS Code: same command when those apps are detected (local MCP config) — https://docs.neus.network/mcp/setup',
559
+ );
560
+ }
551
561
  if ((command === 'init' || command === 'auth') && accessKey) {
552
562
  lines.push('Personal account tools are enabled where the client supports user-scope auth setup.');
553
563
  }
@@ -659,6 +669,97 @@ function runStatus(options) {
659
669
  printResultSummary('status', scope, inspected, '');
660
670
  }
661
671
 
672
+ function runSetup(options) {
673
+ const scope = resolveScope(options);
674
+ ensureSafeAuth('setup', scope, options.accessKey);
675
+ const cwd = process.cwd();
676
+ if (options.project && options.accessKey) {
677
+ throw new Error('Access keys are only supported in user scope. Remove --project or omit --access-key.');
678
+ }
679
+
680
+ const clients = resolveClients(scope, options.clients);
681
+ ensureClientSelection(scope, clients);
682
+
683
+ const initResults = runClientOperations(
684
+ clients,
685
+ scope,
686
+ cwd,
687
+ options.dryRun,
688
+ (client) => installClient(client, scope, options.accessKey, options.dryRun, cwd),
689
+ );
690
+
691
+ const payload = {
692
+ command: 'setup',
693
+ scope,
694
+ detectedClients: defaultUserClients(),
695
+ clients,
696
+ accessKeyConfigured: Boolean(options.accessKey),
697
+ results: initResults,
698
+ hasErrors: initResults.some((result) => result.error),
699
+ };
700
+
701
+ if (options.json) {
702
+ printJson(payload);
703
+ } else {
704
+ printResultSummary('setup', scope, initResults, options.accessKey);
705
+ }
706
+
707
+ if (payload.hasErrors) {
708
+ process.exitCode = 1;
709
+ }
710
+ }
711
+
712
+ function runDoctor(options) {
713
+ const scope = resolveScope(options);
714
+ const cwd = process.cwd();
715
+ const clients = resolveClients(scope, options.clients);
716
+ ensureClientSelection(scope, clients);
717
+
718
+ const inspected = runClientOperations(clients, scope, cwd, options.dryRun, (client) =>
719
+ inspectClient(client, scope, cwd),
720
+ );
721
+ const configuredClients = inspected.filter((r) => r.configured);
722
+ const payload = {
723
+ command: 'doctor',
724
+ scope,
725
+ clients: inspected,
726
+ configuredCount: configuredClients.length,
727
+ accessKeyPresent: Boolean(options.accessKey),
728
+ profileConnectable: false,
729
+ agentVerified: false,
730
+ summary: '',
731
+ hasErrors: inspected.some((result) => result.error),
732
+ };
733
+
734
+ if (options.json) {
735
+ printJson(payload);
736
+ return;
737
+ }
738
+
739
+ printResultSummary('doctor', scope, inspected, '');
740
+
741
+ const lines = [];
742
+ if (configuredClients.length > 0) {
743
+ lines.push(`MCP reachable: ${configuredClients.map((r) => r.client).join(', ')} ready at ${NEUS_MCP_URL}.`);
744
+ } else {
745
+ lines.push('MCP reachable: No clients configured. Run `neus setup` or `neus init` first.');
746
+ process.stdout.write(`\n${lines.join('\n')}\n`);
747
+ process.exit(1);
748
+ }
749
+
750
+ if (options.accessKey) {
751
+ lines.push('Profile connection: auth header present. Connect to the MCP endpoint and run `neus_me` to confirm.');
752
+ } else {
753
+ lines.push(`Profile connection: No access key found. Run \`neus auth --access-key <npk_...>\` (create one at ${NEUS_ACCESS_KEYS_URL}) and reconnect.`);
754
+ }
755
+
756
+ lines.push('Agent verification: Run `neus_agent_link` and `neus_proofs_check` inside the MCP-connected client to verify agent identity and delegation proofs.');
757
+ lines.push('');
758
+ lines.push('Next: Open your editor/IDE, connect to the NEUS MCP endpoint, and run `neus_context`.');
759
+
760
+ process.stdout.write(`\n${lines.join('\n')}\n`);
761
+ }
762
+
662
763
  function main() {
663
764
  try {
664
765
  const { command, options } = parseArgs(process.argv.slice(2));
@@ -679,6 +780,14 @@ function main() {
679
780
  runStatus(options);
680
781
  return;
681
782
  }
783
+ if (command === 'setup') {
784
+ runSetup(options);
785
+ return;
786
+ }
787
+ if (command === 'doctor') {
788
+ runDoctor(options);
789
+ return;
790
+ }
682
791
 
683
792
  process.stderr.write(`Unknown subcommand: ${command}\n`);
684
793
  printUsage(1);
package/client.js CHANGED
@@ -48,6 +48,13 @@ function normalizeBrowserSignerString(raw) {
48
48
  return null;
49
49
  }
50
50
 
51
+ /** Treats SDK placeholder signatures as absent so the protocol can use session or app-link delegation. */
52
+ function isPlaceholderNeusSignature(signature) {
53
+ const s = typeof signature === 'string' ? signature.trim() : '';
54
+ if (!s) return true;
55
+ return /^0x0+$/i.test(s);
56
+ }
57
+
51
58
  const validateVerifierData = (verifierId, data) => {
52
59
  if (!data || typeof data !== 'object') {
53
60
  return { valid: false, error: 'Data object is required' };
@@ -450,10 +457,12 @@ export class NeusClient {
450
457
  throw new ConfigurationError('Invalid wallet provider');
451
458
  }
452
459
 
453
- _getDefaultBrowserWallet() {
454
- if (typeof window === 'undefined') return null;
455
- return window.ethereum || window.solana || (window.phantom && window.phantom.solana) || null;
456
- }
460
+ _getDefaultBrowserWallet() {
461
+ if (typeof window === 'undefined') return null;
462
+ // Legacy convenience fallback only. Non-EVM wallets must be passed explicitly
463
+ // with CAIP-2 chain context so the SDK does not route them through EVM RPC.
464
+ return window.ethereum || null;
465
+ }
457
466
 
458
467
  async _buildPrivateGateAuth({ address, wallet, chain, signatureMethod } = {}) {
459
468
  const providerWallet = wallet || this._getDefaultBrowserWallet();
@@ -595,8 +604,74 @@ export class NeusClient {
595
604
  };
596
605
  }
597
606
 
607
+ async verifyFromApp(params) {
608
+ if (!params || typeof params !== 'object') {
609
+ throw new ValidationError('verifyFromApp requires a params object');
610
+ }
611
+
612
+ const {
613
+ user,
614
+ verifier = 'ownership-basic',
615
+ content,
616
+ data: explicitData = null,
617
+ options = {}
618
+ } = params;
619
+
620
+ if (!user || typeof user !== 'object') {
621
+ throw new ValidationError('verifyFromApp requires user object');
622
+ }
623
+
624
+ const walletAddress =
625
+ user.walletAddress ||
626
+ user.address ||
627
+ user.identity;
628
+ if (!walletAddress || typeof walletAddress !== 'string') {
629
+ throw new ValidationError('verifyFromApp requires user.walletAddress');
630
+ }
631
+
632
+ const delegationQHash =
633
+ this.config.appLinkQHash ||
634
+ (typeof process !== 'undefined' && process.env && process.env.NEUS_APP_LINK_QHASH) ||
635
+ null;
636
+
637
+ let data;
638
+ if (explicitData && typeof explicitData === 'object') {
639
+ data = { owner: walletAddress, ...explicitData };
640
+ } else if (content && typeof content === 'object') {
641
+ data = {
642
+ owner: walletAddress,
643
+ content: JSON.stringify(content),
644
+ contentType: typeof content.contentType === 'string' ? content.contentType : 'application/json',
645
+ reference: content.reference || { type: 'other' }
646
+ };
647
+ } else if (typeof content === 'string') {
648
+ data = {
649
+ owner: walletAddress,
650
+ content,
651
+ reference: { type: 'other' }
652
+ };
653
+ } else {
654
+ data = { owner: walletAddress, reference: { type: 'other' } };
655
+ }
656
+
657
+ const signedTimestamp = Date.now();
658
+ const verifierIds = [verifier];
659
+
660
+ return this.verify({
661
+ verifierIds,
662
+ data,
663
+ walletAddress,
664
+ signedTimestamp,
665
+ ...(delegationQHash ? { delegationQHash } : {}),
666
+ options
667
+ });
668
+ }
669
+
598
670
  async verify(params) {
599
- if ((!params?.signature || !params?.walletAddress) && (params?.verifier || params?.content || params?.data)) {
671
+ if (
672
+ (isPlaceholderNeusSignature(params?.signature) || !params?.signature || !params?.walletAddress) &&
673
+ (params?.verifier || params?.content || params?.data)
674
+ ) {
600
675
  const { content, verifier = 'ownership-basic', data = null, wallet = null, options = {} } = params;
601
676
 
602
677
  if (verifier === 'ownership-basic' && !data && (!content || typeof content !== 'string')) {
@@ -657,10 +732,10 @@ export class NeusClient {
657
732
  walletAddress = Array.isArray(accounts) && accounts.length > 0 ? accounts[0] : null;
658
733
  }
659
734
  }
660
- } else {
661
- if (typeof window === 'undefined' || !window.ethereum) {
662
- throw new ConfigurationError('No Web3 wallet detected. Please install MetaMask or provide wallet parameter.');
663
- }
735
+ } else {
736
+ if (typeof window === 'undefined' || !window.ethereum) {
737
+ throw new ConfigurationError('No EVM browser wallet detected. Provide wallet explicitly for non-EVM flows and include chain as a CAIP-2 value.');
738
+ }
664
739
  await window.ethereum.request({ method: 'eth_requestAccounts' });
665
740
  provider = window.ethereum;
666
741
  const accounts = await provider.request({ method: 'eth_accounts' });
@@ -962,14 +1037,17 @@ export class NeusClient {
962
1037
  verifierIds,
963
1038
  data,
964
1039
  walletAddress,
965
- signature,
1040
+ signature: rawSignature,
966
1041
  signedTimestamp,
967
1042
  chainId,
968
1043
  chain,
969
1044
  signatureMethod,
1045
+ delegationQHash,
970
1046
  options = {}
971
1047
  } = params;
972
1048
 
1049
+ const signature = isPlaceholderNeusSignature(rawSignature) ? undefined : rawSignature;
1050
+
973
1051
  const resolvedChainId = chainId || (chain ? null : NEUS_CONSTANTS.HUB_CHAIN_ID);
974
1052
 
975
1053
  const normalizeVerifierId = (id) => {
@@ -988,8 +1066,12 @@ export class NeusClient {
988
1066
  if (!walletAddress || typeof walletAddress !== 'string') {
989
1067
  throw new ValidationError('walletAddress is required');
990
1068
  }
991
- if (!signature) {
992
- throw new ValidationError('signature is required');
1069
+ const hasAppAttribution =
1070
+ typeof this.config.appId === 'string' && this.config.appId.trim().length > 0;
1071
+ if (!signature && !delegationQHash && !hasAppAttribution) {
1072
+ throw new ValidationError(
1073
+ 'signature, delegationQHash, or NeusClient config appId (sent as X-Neus-App) is required'
1074
+ );
993
1075
  }
994
1076
  if (!signedTimestamp || typeof signedTimestamp !== 'number') {
995
1077
  throw new ValidationError('signedTimestamp is required');
@@ -1019,11 +1101,12 @@ export class NeusClient {
1019
1101
  verifierIds: normalizedVerifierIds,
1020
1102
  data,
1021
1103
  walletAddress,
1022
- signature,
1104
+ ...(signature ? { signature } : {}),
1023
1105
  signedTimestamp,
1024
1106
  ...(resolvedChainId !== null && { chainId: resolvedChainId }),
1025
1107
  ...(chain && { chain }),
1026
1108
  ...(signatureMethod && { signatureMethod }),
1109
+ ...(delegationQHash && { delegationQHash }),
1027
1110
  options: optionsPayload
1028
1111
  };
1029
1112
 
package/errors.js CHANGED
@@ -79,7 +79,7 @@ export class NetworkError extends SDKError {
79
79
  super(message, code);
80
80
  this.name = 'NetworkError';
81
81
  this.originalError = originalError;
82
- this.isRetryable = true; // Network errors are typically retryable
82
+ this.isRetryable = true; // Network errors are retryable
83
83
  }
84
84
 
85
85
  static isNetworkError(error) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@neus/sdk",
3
- "version": "1.0.10",
4
- "description": "Issue and check NEUS trust receipts across apps, agents, gates, payments, and workflows.",
3
+ "version": "1.0.12",
4
+ "description": "NEUS makes trust portable across the internet — so people, apps, and AI agents can prove what is real before access, payout, or execution.",
5
5
  "bin": {
6
6
  "neus": "cli/neus.mjs"
7
7
  },
package/types.d.ts CHANGED
@@ -7,7 +7,9 @@ declare module '@neus/sdk' {
7
7
 
8
8
  export class NeusClient {
9
9
  constructor(config?: NeusClientConfig);
10
-
10
+
11
+ verifyFromApp(params: VerifyFromAppParams): Promise<VerificationResult>;
12
+
11
13
  verify(params: VerifyParams): Promise<VerificationResult>;
12
14
 
13
15
  getProof(qHash: string): Promise<StatusResult>;
@@ -57,6 +59,8 @@ declare module '@neus/sdk' {
57
59
  apiUrl?: string;
58
60
  apiKey?: string;
59
61
  appId?: string;
62
+ /** Optional: only when using legacy `delegationQHash` on verify requests. App-linked servers rely on `appId` + Origin + stored delegation. */
63
+ appLinkQHash?: string;
60
64
  paymentSignature?: string;
61
65
  extraHeaders?: Record<string, string>;
62
66
  timeout?: number;
@@ -92,6 +96,18 @@ declare module '@neus/sdk' {
92
96
  meta?: Record<string, any>;
93
97
  }
94
98
 
99
+ export interface VerifyFromAppParams {
100
+ user: {
101
+ walletAddress?: string;
102
+ address?: string;
103
+ identity?: string;
104
+ };
105
+ verifier?: VerifierId;
106
+ content?: string | Record<string, any>;
107
+ data?: VerificationData;
108
+ options?: VerifyOptions;
109
+ }
110
+
95
111
  export interface VerifyParams {
96
112
  verifier?: VerifierId;
97
113
  content?: string;
@@ -105,6 +121,7 @@ declare module '@neus/sdk' {
105
121
  chainId?: number;
106
122
  chain?: string;
107
123
  signatureMethod?: string;
124
+ delegationQHash?: string;
108
125
  wallet?: WalletLike;
109
126
  }
110
127
 
package/widgets/README.md CHANGED
@@ -10,7 +10,7 @@ npm install @neus/sdk react react-dom
10
10
 
11
11
  ## VerifyGate
12
12
 
13
- Create mode defaults to **private**. Override `proofOptions` only when you intentionally need public visibility for link-based or public checks.
13
+ Create mode defaults to **private**. Override `proofOptions` only when you need public visibility for link-based or public checks.
14
14
 
15
15
  ```jsx
16
16
  import { VerifyGate } from '@neus/sdk/widgets';
@@ -307,7 +307,7 @@ function VerifyGate({
307
307
  return provider.address;
308
308
  }
309
309
  if (typeof provider.request !== "function") {
310
- throw new Error("No wallet provider available");
310
+ throw new Error("Connect a wallet and try again.");
311
311
  }
312
312
  let accounts = await provider.request({ method: "eth_accounts" });
313
313
  if (!accounts || accounts.length === 0) {
@@ -315,7 +315,7 @@ function VerifyGate({
315
315
  accounts = await provider.request({ method: "eth_accounts" });
316
316
  }
317
317
  if (!accounts || accounts.length === 0) {
318
- throw new Error("No wallet accounts available");
318
+ throw new Error("Connect a wallet and try again.");
319
319
  }
320
320
  return accounts[0];
321
321
  }, [wallet]);
@@ -388,7 +388,7 @@ function VerifyGate({
388
388
  }, [client, buildGateRequirements, wallet, inferChainFromAddress, signatureMethod]);
389
389
  const launchHostedCheckout = useCallback(async () => {
390
390
  if (typeof window === "undefined") {
391
- throw new Error("Hosted checkout is only available in browser environments");
391
+ throw new Error("Open this in a browser to verify.");
392
392
  }
393
393
  const origin = window.location.origin;
394
394
  const returnUrl = window.location.href;
@@ -446,7 +446,7 @@ function VerifyGate({
446
446
  completed = true;
447
447
  cleanup();
448
448
  if (payload?.eligible === false) {
449
- reject(new Error("Hosted checkout completed but eligibility was not satisfied."));
449
+ reject(new Error("Verification could not be completed."));
450
450
  return;
451
451
  }
452
452
  resolve(payload);
@@ -573,7 +573,7 @@ function VerifyGate({
573
573
  const buildDataForVerifier = (verifierId) => {
574
574
  if (!CREATABLE_VERIFIERS.has(verifierId)) {
575
575
  throw new Error(
576
- `${verifierId} cannot be created via the wallet flow. It requires hosted checkout or a server integration.`
576
+ "This check requires the hosted verifier."
577
577
  );
578
578
  }
579
579
  const explicit = verifierData && verifierData[verifierId];
@@ -587,12 +587,12 @@ function VerifyGate({
587
587
  if (verifierId === "wallet-link") {
588
588
  if (!explicit?.secondaryWalletAddress || !explicit?.signature || !explicit?.chain || !explicit?.signatureMethod) {
589
589
  throw new Error(
590
- "wallet-link direct mode requires verifierData: { secondaryWalletAddress, signature, chain, signatureMethod }. For user-facing flows, prefer hosted checkout."
590
+ "Missing required wallet details."
591
591
  );
592
592
  }
593
593
  return explicit;
594
594
  }
595
- throw new Error(`${verifierId} requires explicit verifierData`);
595
+ throw new Error("Missing required verification details.");
596
596
  };
597
597
  const verifyOne = async (verifierId) => {
598
598
  const dataForVerifier = buildDataForVerifier(verifierId);
@@ -616,7 +616,7 @@ function VerifyGate({
616
616
  const verifiedVerifiers = final?.data?.verifiedVerifiers || [];
617
617
  const verifierResult = verifiedVerifiers.find((v) => v.verifierId === verifierId);
618
618
  if (!verifierResult || verifierResult.verified !== true) {
619
- throw new Error(`Verification failed for ${verifierId}`);
619
+ throw new Error("Verification could not be completed.");
620
620
  }
621
621
  const hubTx = final?.data?.hubTransaction || {};
622
622
  const crosschain = final?.data?.crosschain || {};
@@ -849,7 +849,7 @@ function VerifyGate({
849
849
  textUnderlineOffset: "2px",
850
850
  opacity: disabled || isProcessing ? 0.6 : 0.9
851
851
  },
852
- children: "Already verified? Sign to reuse existing proofs."
852
+ children: "Already verified? Reuse your proof."
853
853
  }
854
854
  ),
855
855
  error && /* @__PURE__ */ jsx("div", { style: {