@neus/sdk 1.0.6 → 1.0.8

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
@@ -10,29 +10,6 @@
10
10
  npm install @neus/sdk
11
11
  ```
12
12
 
13
- ## One-command onboarding
14
-
15
- ```bash
16
- npx -y -p @neus/sdk neus init
17
- ```
18
-
19
- Configures supported MCP clients automatically. By default the command installs NEUS into user-level Claude Code, Cursor, and VS Code MCP config when those clients are detected.
20
-
21
- ## CLI
22
-
23
- ```bash
24
- # Autopilot setup for detected clients
25
- npx -y -p @neus/sdk neus init
26
-
27
- # Enable personal account tools such as neus_me and private reads
28
- npx -y -p @neus/sdk neus auth --access-key <npk_...>
29
-
30
- # Inspect current NEUS MCP setup
31
- npx -y -p @neus/sdk neus status --json
32
- ```
33
-
34
- Use `neus init --project` when you want shared repo config instead of personal user-scope setup. Access keys stay user-scope only so secrets do not land in checked-in config.
35
-
36
13
  ## Minimal working example
37
14
 
38
15
  ```javascript
package/SECURITY.md CHANGED
@@ -1,38 +1,38 @@
1
- # NEUS SDK security notes
2
-
3
- Treat **wallet signatures** and **API keys** as secrets. Do not log them, expose them to clients, or store them in analytics.
4
-
5
- ## Authentication model
6
-
7
- - **Verification requests** are authenticated with a wallet signature over the **CAIP-380 Portable Proof** six-line signing string. Never roll your own message format in production—use the SDK or the hosted preparation step documented for HTTP integrations.
8
- - **Proof lookups by `proofId`** are safe for public proofs. Private proofs return a minimal payload unless the caller proves ownership (authenticated owner or signed request).
9
- - **Owner-only reads** of private proof payloads require an extra owner-signed request. The SDK attaches the required signed headers for you.
10
-
11
- ## Do not
12
-
13
- - Do not treat proof signatures as bearer tokens (they are request-bound).
14
- - Do not embed API keys in browser apps. Keep API keys server-side only.
15
- - Do not log or persist proof signatures, API keys, or third-party auth credentials (if your integration uses them).
16
-
17
- ## Privacy defaults
18
-
19
- **`client.verify()`** defaults to **private**.
20
-
21
- **`VerifyGate`** create mode also defaults to **private**.
22
-
23
- Use public visibility only when you intentionally need proof reuse without owner-authenticated access:
24
-
25
- - unlisted public: `privacyLevel: 'public'`, `publicDisplay: false`
26
- - listed public: `privacyLevel: 'public'`, `publicDisplay: true`
27
-
28
- Do not treat unlisted public proofs as secret.
29
-
30
- `storeOriginalContent` is an advanced storage control. Most integrations should leave the default as-is.
31
-
32
- Controls:
33
-
34
- - `privacyLevel` - private by default; switch to public only for intentional public reuse
35
- - `publicDisplay` - discovery vs unlisted
36
- - `storeOriginalContent` - advanced content-storage control
37
-
38
- Discoverable listings require **`privacyLevel: 'public'`** and **`publicDisplay: true`**.
1
+ # NEUS SDK security notes
2
+
3
+ Treat **wallet signatures** and **API keys** as secrets. Do not log them, expose them to clients, or store them in analytics.
4
+
5
+ ## Authentication model
6
+
7
+ - **Verification requests** are authenticated with a wallet signature over the **CAIP-380 Portable Proof** six-line signing string. Never roll your own message format in production—use the SDK or the hosted preparation step documented for HTTP integrations.
8
+ - **Proof lookups by `proofId`** are safe for public proofs. Private proofs return a minimal payload unless the caller proves ownership (authenticated owner or signed request).
9
+ - **Owner-only reads** of private proof payloads require an extra owner-signed request. The SDK attaches the required signed headers for you.
10
+
11
+ ## Do not
12
+
13
+ - Do not treat proof signatures as bearer tokens (they are request-bound).
14
+ - Do not embed API keys in browser apps. Keep API keys server-side only.
15
+ - Do not log or persist proof signatures, API keys, or third-party auth credentials (if your integration uses them).
16
+
17
+ ## Privacy defaults
18
+
19
+ **`client.verify()`** defaults to **private**.
20
+
21
+ **`VerifyGate`** create mode also defaults to **private**.
22
+
23
+ Use public visibility only when you intentionally need proof reuse without owner-authenticated access:
24
+
25
+ - unlisted public: `privacyLevel: 'public'`, `publicDisplay: false`
26
+ - listed public: `privacyLevel: 'public'`, `publicDisplay: true`
27
+
28
+ Do not treat unlisted public proofs as secret.
29
+
30
+ `storeOriginalContent` is an advanced storage control. Most integrations should leave the default as-is.
31
+
32
+ Controls:
33
+
34
+ - `privacyLevel` - private by default; switch to public only for intentional public reuse
35
+ - `publicDisplay` - discovery vs unlisted
36
+ - `storeOriginalContent` - advanced content-storage control
37
+
38
+ Discoverable listings require **`privacyLevel: 'public'`** and **`publicDisplay: true`**.
package/cli/neus.mjs CHANGED
@@ -24,9 +24,17 @@ function jsonStringify(value) {
24
24
 
25
25
  function readJsonFile(targetPath, fallback) {
26
26
  if (!fileExists(targetPath)) return fallback;
27
- const raw = fs.readFileSync(targetPath, 'utf8');
28
- const parsed = JSON.parse(raw);
29
- return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : fallback;
27
+ const raw = fs.readFileSync(targetPath, 'utf8').trim();
28
+ if (!raw) return fallback;
29
+ try {
30
+ const parsed = JSON.parse(raw);
31
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : fallback;
32
+ } catch (error) {
33
+ if (error instanceof SyntaxError) {
34
+ throw new Error(`Invalid JSON in ${targetPath}`);
35
+ }
36
+ throw error;
37
+ }
30
38
  }
31
39
 
32
40
  function writeJsonFile(targetPath, nextValue, dryRun) {
@@ -306,6 +314,7 @@ function installCursor(scope, accessKey, dryRun, cwd) {
306
314
  targetPath,
307
315
  backupPath: writeResult.backupPath,
308
316
  dryRun,
317
+ error: null,
309
318
  };
310
319
  }
311
320
 
@@ -329,6 +338,7 @@ function installVsCode(scope, accessKey, dryRun, cwd) {
329
338
  targetPath,
330
339
  backupPath: writeResult.backupPath,
331
340
  dryRun,
341
+ error: null,
332
342
  };
333
343
  }
334
344
 
@@ -352,6 +362,7 @@ function installClaudeProject(scope, accessKey, dryRun, cwd) {
352
362
  targetPath,
353
363
  backupPath: writeResult.backupPath,
354
364
  dryRun,
365
+ error: null,
355
366
  };
356
367
  }
357
368
 
@@ -387,6 +398,7 @@ function installClaudeUser(scope, accessKey, dryRun, cwd) {
387
398
  targetPath: '~/.claude.json',
388
399
  backupPath: null,
389
400
  dryRun,
401
+ error: null,
390
402
  };
391
403
  }
392
404
 
@@ -407,7 +419,7 @@ function installClient(client, scope, accessKey, dryRun, cwd) {
407
419
  function inspectCursor(scope, cwd) {
408
420
  const targetPath = cursorConfigPath(scope, cwd);
409
421
  if (!fileExists(targetPath)) {
410
- return { client: 'cursor', scope, configured: false, authConfigured: false, targetPath };
422
+ return { client: 'cursor', scope, configured: false, authConfigured: false, targetPath, error: null };
411
423
  }
412
424
  const doc = readJsonFile(targetPath, {});
413
425
  const server = doc.mcpServers?.[NEUS_SERVER_NAME];
@@ -417,13 +429,14 @@ function inspectCursor(scope, cwd) {
417
429
  configured: Boolean(server && server.url === NEUS_MCP_URL),
418
430
  authConfigured: Boolean(server?.headers?.Authorization),
419
431
  targetPath,
432
+ error: null,
420
433
  };
421
434
  }
422
435
 
423
436
  function inspectVsCode(scope, cwd) {
424
437
  const targetPath = vscodeConfigPath(scope, cwd);
425
438
  if (!fileExists(targetPath)) {
426
- return { client: 'vscode', scope, configured: false, authConfigured: false, targetPath };
439
+ return { client: 'vscode', scope, configured: false, authConfigured: false, targetPath, error: null };
427
440
  }
428
441
  const doc = readJsonFile(targetPath, {});
429
442
  const server = doc.servers?.[NEUS_SERVER_NAME];
@@ -433,6 +446,7 @@ function inspectVsCode(scope, cwd) {
433
446
  configured: Boolean(server && server.url === NEUS_MCP_URL),
434
447
  authConfigured: Boolean(server?.headers?.Authorization),
435
448
  targetPath,
449
+ error: null,
436
450
  };
437
451
  }
438
452
 
@@ -440,7 +454,7 @@ function inspectClaude(scope, cwd) {
440
454
  if (scope === 'project') {
441
455
  const targetPath = claudeProjectConfigPath(cwd);
442
456
  if (!fileExists(targetPath)) {
443
- return { client: 'claude', scope, configured: false, authConfigured: false, targetPath };
457
+ return { client: 'claude', scope, configured: false, authConfigured: false, targetPath, error: null };
444
458
  }
445
459
  const doc = readJsonFile(targetPath, {});
446
460
  const server = doc.mcpServers?.[NEUS_SERVER_NAME];
@@ -450,11 +464,12 @@ function inspectClaude(scope, cwd) {
450
464
  configured: Boolean(server && server.url === NEUS_MCP_URL),
451
465
  authConfigured: Boolean(server?.headers?.Authorization),
452
466
  targetPath,
467
+ error: null,
453
468
  };
454
469
  }
455
470
 
456
471
  if (!commandExists('claude')) {
457
- return { client: 'claude', scope, configured: false, authConfigured: null, targetPath: '~/.claude.json' };
472
+ return { client: 'claude', scope, configured: false, authConfigured: null, targetPath: '~/.claude.json', error: null };
458
473
  }
459
474
 
460
475
  const result = runCommand('claude', ['mcp', 'list'], cwd, true);
@@ -465,6 +480,7 @@ function inspectClaude(scope, cwd) {
465
480
  configured,
466
481
  authConfigured: null,
467
482
  targetPath: '~/.claude.json',
483
+ error: null,
468
484
  };
469
485
  }
470
486
 
@@ -479,9 +495,47 @@ function printJson(payload) {
479
495
  process.stdout.write(jsonStringify(payload));
480
496
  }
481
497
 
498
+ function clientTargetPath(client, scope, cwd) {
499
+ if (client === 'cursor') return cursorConfigPath(scope, cwd);
500
+ if (client === 'vscode') return vscodeConfigPath(scope, cwd);
501
+ if (client === 'claude') {
502
+ return scope === 'project' ? claudeProjectConfigPath(cwd) : '~/.claude.json';
503
+ }
504
+ return null;
505
+ }
506
+
507
+ function errorMessage(error) {
508
+ return error instanceof Error ? error.message : String(error || 'Unknown error');
509
+ }
510
+
511
+ function buildClientFailure(client, scope, cwd, dryRun, error) {
512
+ return {
513
+ client,
514
+ scope,
515
+ configured: false,
516
+ authConfigured: false,
517
+ changed: false,
518
+ targetPath: clientTargetPath(client, scope, cwd),
519
+ backupPath: null,
520
+ dryRun,
521
+ error: errorMessage(error),
522
+ };
523
+ }
524
+
525
+ function runClientOperations(clients, scope, cwd, dryRun, runner) {
526
+ return clients.map((client) => {
527
+ try {
528
+ return runner(client);
529
+ } catch (error) {
530
+ return buildClientFailure(client, scope, cwd, dryRun, error);
531
+ }
532
+ });
533
+ }
534
+
482
535
  function printResultSummary(command, scope, results, accessKey) {
483
536
  const changedCount = results.filter((result) => result.changed).length;
484
- const configuredClients = results.map((result) => result.client).join(', ');
537
+ const configuredClients = results.filter((result) => result.configured).map((result) => result.client).join(', ');
538
+ const failures = results.filter((result) => result.error);
485
539
  const lines = [
486
540
  `NEUS ${command} completed for ${results.length} client${results.length === 1 ? '' : 's'} in ${scope} scope.`,
487
541
  `Configured: ${configuredClients || 'none'}.`,
@@ -501,6 +555,9 @@ function printResultSummary(command, scope, results, accessKey) {
501
555
  const enabled = results.filter((result) => result.configured).map((result) => result.client);
502
556
  lines.push(`Active: ${enabled.length > 0 ? enabled.join(', ') : 'none'}.`);
503
557
  }
558
+ if (failures.length > 0) {
559
+ lines.push(`Issues: ${failures.map((result) => `${result.client}: ${result.error}`).join(' | ')}`);
560
+ }
504
561
 
505
562
  process.stdout.write(`${lines.join('\n')}\n`);
506
563
  }
@@ -508,11 +565,18 @@ function printResultSummary(command, scope, results, accessKey) {
508
565
  function runInit(options) {
509
566
  const scope = resolveScope(options);
510
567
  ensureSafeAuth('init', scope, options.accessKey);
568
+ const cwd = process.cwd();
511
569
 
512
570
  const clients = resolveClients(scope, options.clients);
513
571
  ensureClientSelection(scope, clients);
514
572
 
515
- const results = clients.map((client) => installClient(client, scope, options.accessKey, options.dryRun, process.cwd()));
573
+ const results = runClientOperations(
574
+ clients,
575
+ scope,
576
+ cwd,
577
+ options.dryRun,
578
+ (client) => installClient(client, scope, options.accessKey, options.dryRun, cwd),
579
+ );
516
580
  const payload = {
517
581
  command: 'init',
518
582
  scope,
@@ -520,18 +584,24 @@ function runInit(options) {
520
584
  clients,
521
585
  accessKeyConfigured: Boolean(options.accessKey),
522
586
  results,
587
+ hasErrors: results.some((result) => result.error),
523
588
  };
524
589
 
525
590
  if (options.json) {
526
591
  printJson(payload);
527
- return;
592
+ } else {
593
+ printResultSummary('init', scope, results, options.accessKey);
594
+ }
595
+
596
+ if (payload.hasErrors) {
597
+ process.exitCode = 1;
528
598
  }
529
- printResultSummary('init', scope, results, options.accessKey);
530
599
  }
531
600
 
532
601
  function runAuth(options) {
533
602
  const scope = resolveScope(options);
534
603
  ensureSafeAuth('auth', scope, options.accessKey);
604
+ const cwd = process.cwd();
535
605
  if (!options.accessKey) {
536
606
  throw new Error(`Missing access key. Create one at ${NEUS_ACCESS_KEYS_URL} and rerun neus auth --access-key <npk_...>.`);
537
607
  }
@@ -539,32 +609,47 @@ function runAuth(options) {
539
609
  const clients = resolveClients(scope, options.clients);
540
610
  ensureClientSelection(scope, clients);
541
611
 
542
- const results = clients.map((client) => installClient(client, scope, options.accessKey, options.dryRun, process.cwd()));
612
+ const results = runClientOperations(
613
+ clients,
614
+ scope,
615
+ cwd,
616
+ options.dryRun,
617
+ (client) => installClient(client, scope, options.accessKey, options.dryRun, cwd),
618
+ );
543
619
  const payload = {
544
620
  command: 'auth',
545
621
  scope,
546
622
  clients,
547
623
  accessKeyConfigured: true,
548
624
  results,
625
+ hasErrors: results.some((result) => result.error),
549
626
  };
550
627
 
551
628
  if (options.json) {
552
629
  printJson(payload);
553
- return;
630
+ } else {
631
+ printResultSummary('auth', scope, results, options.accessKey);
632
+ }
633
+
634
+ if (payload.hasErrors) {
635
+ process.exitCode = 1;
554
636
  }
555
- printResultSummary('auth', scope, results, options.accessKey);
556
637
  }
557
638
 
558
639
  function runStatus(options) {
559
640
  const scope = resolveScope(options);
641
+ const cwd = process.cwd();
560
642
  const clients = resolveClients(scope, options.clients);
561
643
  ensureClientSelection(scope, clients);
562
644
 
563
- const inspected = clients.map((client) => inspectClient(client, scope, process.cwd()));
645
+ const inspected = runClientOperations(clients, scope, cwd, options.dryRun, (client) =>
646
+ inspectClient(client, scope, cwd),
647
+ );
564
648
  const payload = {
565
649
  command: 'status',
566
650
  scope,
567
651
  clients: inspected,
652
+ hasErrors: inspected.some((result) => result.error),
568
653
  };
569
654
 
570
655
  if (options.json) {